并发容器
并发原理
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; } }Copy to clipboardErrorCopied 复制代码
普通容器转换
synchronizedList 方法
如果遇到多个线程操作同一个容器的场景,可以通过 Collections 工具类中的 synchronizedList 方法将其转换成线程安全的容器。
方法会被 synchronized 关键字重定义。