Concurrent包总结——线程安全的集合操作

本文详细介绍了Java中线程安全的集合类,包括CopyOnWriteArraySet、CopyOnWriteArrayList、ConcurrentLinkedQueue等,并深入探讨了BlockingQueue及其几种实现方式的特点与应用场景。

java中提供了丰富的集合类操作,大概可以分为无序结合Set,有序集合List和无序键值对集合Map。Java5之后又新增了队列操作集合Queue。Java1.5之后新增了线程安全的集合操作类,阻止在java.util.concurrent包中。本文仅仅探讨该包下面的线程安全的结合操作类。

先看下concurrent包下面线程安全类的类图结构:

   

1.CopyOnWriteArraySet类

CopyOnWriteArrayList类的底层是通过CopyOnWriteArrayList来实现的。因此它与CopyOnWriteArrayList有相同的性质:

  • 当元素数目比较小,并且读操作远远大于写操作时候适合用该集合类
  • 它是线程安全的
  • 写操作(add(),set(),remove(),etc)代价比较高昂,效率也比较低。因为在写操作的时候,其实现是依赖底层数组的复制。
  • 迭代器不支持可变删除操作。
  • 构造迭代器的时候依赖于底层不变数组的快照,因此进行遍历的时候,遍历速度很快并且不会与其他线程发生冲突。

   

2.CopyOnWriteArrayList类

CopyOnWriteArrayList是一个线程安全类的变体,所有的写操作都是通过对底层数组的复制来完成的,因此该集合类的写操作代价非常高昂。但是CopyOnWriteArrayList也正是通过上述快照的方式来实现了读操作的高并发性。因为底层代码的实现是通过一个数组来存储元素,因此集合总是返回调用方法那一刻数组中对应的元素。对于其他线程的并发写操作而忽视。

在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。”快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内绝不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。自创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。不支持迭代器上更改元素的操作(移除、设置和添加)。这些方法将抛出 UnsupportedOperationException。

   

3.ConcurrentLinkedQueue

一个基于链接节点的、无界的、线程安全的队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部是队列中时间最长的元素。队列的尾部是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。

此实现采用了有效的”无等待 (wait-free)”算法。与大多数 collection 不同,size 方法不是 一个固定时间的操作。由于这些队列的异步特性,确定当前元素的数量需要遍历这些元素。

且该队列集合的操作是非阻塞的。

   

4.BlockingQueue接口

BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待于唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。

   

BlockingQueue的特点:

  • BlockingQueue是一个阻塞队列,即如果该队列是空的,则在调用方法该队列中取出元素的线程将会被阻塞进入阻塞状态,直到该队列中被放入新的元素才会唤醒阻塞线程;反之如果一个BlockingQueue已经满了,则调用方法向该队列中添加元素的线程同样会被阻塞进入阻塞状态,知道队列中有多余的空间才会被唤醒。
  • BlockingQueue不接受null元素
  • BlockingQueue可以是限定容量的
  • 主要用于生产者——消费者模式
  • BlockingQueue的实现是线程安全的

   

BlockingQueue的常用方法:

add(E o):将指定的元素插入到队列中(如果队列仍有空间),执行成功之后返回true,否则则直接抛出IllegalSatateException.

offer(E o):将指定的元素插入到队列中,成功则返回true,否则返回false

put(E o):指定的元素插入的队列中,如果队列中没有空间则一直等待。(阻塞的)

poll(long timeout, TimeUnit unit):检索并移除此队列的头部,如果此队列中没有任何元素,则等待指定等待的时间(如果有必要)。

take():检索并移除此队列的头部,如果此队列不存在任何元素,则一直等待。(阻塞的)

   

4.1 ArrayBlockingQueue(有界缓存,FIFO,支持公平访问策略)

(1)一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。

(2)队列的头部 是在队列中存在时间最长的元素,队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素。

(3)这是一个典型的”有界缓存区“,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致放入操作受阻塞;试图从空队列中检索元素将导致类似阻塞。

(4)此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了”不平衡性”。

   

4.2 LinkedBlockingQueue(无界)

(1)基于链表的阻塞队列,容量是任意的。提供可选的指定容量的构造方法来防止队列过度扩张,如果未指定容量,则它的容量等于Integer.MAX_VALUE。除非插入的节点已经超过队列容量,否则每次插入都会动态的插入一个新的节点。

(2)该队列在存储元素的时候也是按照FIFO原则排序。队列的头部元素在队列中呆时间最长,尾部元素则时间最短,每次插入元素会插入到队列的尾部。

   

4.3 DelayQueue(无界)

(1)Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。

(2)该队列的头部 是延迟期满后保存时间最长的 Delayed 元素(不是最先放入的元素)。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。

(3)当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满。

(4)该队列的元素必须实习那Delayed接口。

   

4.4 PriorityBlockingQueue(无界)

(1)一个无界的阻塞队列,它使用与类 PriorityQueue 相同的顺序规则:基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),并且提供了阻塞检索的操作。

(2)虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会失败(导致 OutOfMemoryError)。

(3)此类不允许使用 null 元素。

(4)依赖自然顺序的优先级队列也不允许插入不可比较的对象(因为这样做会抛出 ClassCastException)。

   

4.5 SynchronousQueue

(1)一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。

(2)不能在同步队列上进行 peek,因为仅在试图要取得元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)添加元素;也不能迭代队列,因为其中没有元素可用于迭代。

(3)队列的头 是尝试添加到队列中的首个已排队线程元素;如果没有已排队线程,则不添加元素并且头为 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空集合。此队列不允许 null 元素。

(4)同步队列非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。

(5)对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。

   

                </div>

转自:https://blog.youkuaiyun.com/e01014165/article/details/52169792

### Java线程安全集合类及其实现方式 #### 1. 集合框架中的线程安全性 Java集合框架中并非所有的集合都默认提供线程安全保障。一些常见的集合类如`ArrayList`、`LinkedList`和`HashMap`等是非线程安全的[^2]。为了在多线程环境中使用这些集合而不引发数据一致性问题,开发者可以选择不同的策略来实现线程安全。 #### 2. 同步装器 Java标准库提供了通过`Collections`工具类的方法将非线程安全集合转换为线程安全版本的功能。例如: - `Collections.synchronizedList(List<T> list)` 可以将一个普通的列表转为线程安全的列表。 - `Collections.synchronizedSet(Set<T> set)` 将集合同步化。 - `Collections.synchronizedMap(Map<K,V> map)` 对映射表进行同步处理。 这种方法虽然简单易用,但在高并发场景下性能可能较差,因为每次访问都需要加锁解锁操作[^3]。 ```java // 使用同步装器创建线程安全的 List 和 Map 示例 List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>()); ``` #### 3. 并发 (java.util.concurrent) 中的线程安全集合 除了上述基本的同步封装外,JDK还引入了一个专门用于解决高性能并发需求的——`java.util.concurrent`。该含了多种针对特定用途优化过的线程安全集合类,比如: - **CopyOnWriteArrayList**: 当读取频率远高于修改频率时非常有效;它的工作原理是在写入时复制整个数组副本并更新新副本的内容后再替换旧副本[^4]。 ```java // 创建一个线程安全的可变长度数组 List<String> threadSafeList = new CopyOnWriteArrayList<>(); ``` - **ConcurrentHashMap**: 提供了高度优化的数据结构设计,允许更高的并发级别而无需完全锁定整个哈希表。其内部采用了分段锁技术(Segmented Locking),即把整个散列划分为若干个小部分分别上锁管理,从而减少争用情况下的等待时间[^1]。 ```java // 初始化 ConcurrentHashMap 实例 Map<Integer, String> concurrentMap = new ConcurrentHashMap<>(); // 插入键值对 concurrentMap.put(1, "Value"); // 获取指定 key 的 value 值 String result = concurrentMap.get(1); ``` - **BlockingQueue 接口及其具体实现**:适用于生产者消费者模式的应用场合。像 LinkedBlockingQueue 或 ArrayBlockingQueue 这样的队列实现了阻塞功能,在尝试向已满或者为空的队列执行插入/删除动作失败时会自动挂起当前线程直到条件满足为止。 ```java BlockingQueue<String> queue = new LinkedBlockingQueue<>(10); try { queue.put("Message"); // 如果队列满了,则此调用会被阻塞直至空间可用 } catch (InterruptedException e) {} ``` 综上所述,对于不同类型的业务逻辑以及具体的性能考量因素而言,选择合适的线程安全集合至关重要。如果只是偶尔发生少量竞争状况的话,那么简单的同步机制或许已经足够应付局面;然而一旦涉及到频繁存取且规模较大的共享资源情形之下,则应该优先考虑采用更先进的解决方案诸如那些来自 java.util.concurrent 内的组件们。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值