并发原理

Java 提供的基础容器都是线程不安全的,如果并发条件下多个线程同时对一个容器中的数据进行操作,可能会导致各种意想不到的错误。

因此 Java 又提供了一些并发容器在多线程情况下使用,这些并发容器都位于 java.util.concurrent 包内,使用时需要进行导入。


List 接口

Vector 类(已过时)

【数组序列】和 ArrayList 类类似,实现了 List 接口。内部使用 Object 数组存储。

Vector 类内部所有方法都是同步(synchronized) 的,因此线程安全。但高并发场景下非常容易阻塞,性能很差。

CopyOnWriteArrayList 类

【数组序列】和 ArrayList 类类似,实现了 List 接口。内部使用 Object 数组存储。

CopyOnWriteArrayList 对读取操作不上锁,对写入操作上锁。写入操作通过创建数组的副本来实现,修改的内容写入副本后再替换原来的数据。

不允许同时写,但读操作和写操作不冲突,在多读的场合性能非常好。

ConcurrentLinkedQueue 类

【链表序列】和 LinkedList 类类似,实现了 List 以及 Deque 接口。内部使用双向链表存储。

ConcurrentLinkedQueue 类非阻塞,通过 CAS 算法实现线程安全,尝试更新数据时会对数据进行比对。高并发场景下如果加锁的代价很高,可以达到很好的性能。


BlockingQueue 接口

【阻塞队列】被广泛使用在“生产者-消费者”问题中,其原因是 BlockingQueue 提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。

ArrayBlockingQueue 类

有界队列实现类,底层采用数组来实现。ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。

LinkedBlockingQueue 类

PriorityBlockingQueue 类


Map 接口

HashTable 类(已过时)

【哈希表】和 HashMap 类类似,实现了 Map 接口。

HashTable 类内部所有方法都是同步(synchronized) 的,因此线程安全。但由于整个哈希存储区域共享一把锁,高并发场景下非常容易阻塞,性能很差。

ConcurrentHashMap 类

【哈希表】和 HashMap 类类似,实现了 Map 接口。

并发控制使用 synchronized 和 CAS 来操作,采取分段锁机制。synchronized 对哈希存储区域的每个 key 分别上锁,只锁定当前链表或红黑二叉树的首节点,这样只要不发生哈希冲突就不会产生并发,效率大大提升。

JDK 1.7 中, ConcurrentHashMap 类中包含静态内部类 Segment,继承于 ReentrantLock 类用来充当锁的角色,每个 Segment 对象守护若干个保存键值对的链表,共同构成 ConcurrentHashMap 实例中的数组。在 HashEntry 类中,key,hash 和 next 域都被声明为 final 型,value 域被声明为 volatile 型。由于 HashEntry 的 next 域为 final 型,所以新节点只能在链表的表头处插入。

JDK 1.8 中放弃了 Segment 臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。

static final class HashEntry<K,V> {
       final K key;                       // 声明 key 为 final 型
       final int hash;                   // 声明 hash 值为 final 型
       volatile V value;                 // 声明 value 为 volatile 型
       final HashEntry<K,V> next;      // 声明 next 为 final 型
  
       HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
           this.key = key;
           this.hash = hash;
           this.next = next;
           this.value = value;
       }
}

普通容器转换

synchronizedList 方法

如果遇到多个线程操作同一个容器的场景,可以通过 Collections 工具类中的 synchronizedList 方法将其转换成线程安全的容器。

方法会被 synchronized 关键字重定义。