有关List的线程安全、高效读取:不变模式下的CopyOnWriteArrayList类、数据共享通道:BlockingQueue

本文探讨了Java中线程安全的List(如Vector和CopyOnWriteArrayList)以及非线程安全但可同步的LinkedList。重点介绍了ConcurrentLinkedQueue作为高性能队列和数据共享通道,以及BlockingQueue的阻塞特性在多线程协作中的应用。


有关List的线程安全

队列、链表之类的数据结构也是极常用的,几乎所有的应用程序都会与之相关。在java中,
ArrayList和Vector都使用数组作为其内部实现。两者最大的不同在与Vector是线程安全的。
而ArrayList不是。此外LinkedList使用链表的数据结构实现了List。但是LinkedList并
不是线程安全的。参考对HashMap的包装,这里我们也可用用Collections.synchronizedList
方法来包装任意List:
public static List<String> l = Collections.synchronizedList(new LinkedList<String>());
此时生成的List对象就是线程安全的了。

ConcurrentLinkedQueue来实现高并非的队列。应该算是高并发环境中性能最好的队列。
它之所以很好性能,是因为其内部复杂的实现。这个方法没有任何锁操作。
线程安全完全由CAS操作和队列的算法来保证。整个方法的核心是for循环。
这个循环没有出口,直到尝试成功。这也符合CAS操作的流程。

高效读取:不变模式下的CopyOnWriteArrayList类

在很多应用场景中,读操作可能会远远大于写操作。
比如,有些系统级别的信息,往往只需要加载或者修改很少的次数,但是
会被系统内所有模块频繁访问。
由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种
资源浪费。JDK中提供了CopyOnWriteArrayList类,对它来说,读取是完全不用
加锁的,并且更好的消息是,写入也不会阻塞读取操作。只有写入和写入之间需要
进行同步等待。

数据共享通道:BlockingQueue
前面提到了ConcurrentLinkedQueue类是高性能的队列。对于并发程序而言,
高性能自然是一个我们需要追求的目标,但多线程开发模式还会引入一个问题,
那就是如何进行多个线程间的数据共享呢?比如,线程A希望给线程B发一条消息
用什么方式告知线程B是比较合理的呢?
一般来说,我们总希望整个系统是松散耦合的。
比如,你所在小区的物业希望可用得到一些业主的意见,设立一个
意见箱,如果对物业有任何要求或意见都可用投到意见箱里。
如果对物业有任务要求或者意见都可用投到意见箱里。
作为业主的你并不需要直接找到物业相关的工作人员
就能表达意见。实际上,物业的工作人员也可能经常发生变动。
直接找工作人员未必是一件方便的事情。而你投递到意见箱的意见总是
会被物业的工作人员看到,不管是否发生了人员的变动。
这样你就可用很容易地表达自己的诉求了。
你既不需要直接和他们对话,又可用轻松提出自己的建议。

将这个模式映射到我们程序中,就是说我们既希望线程A能够通知线程B,
又希望线程A不知道线程B的存在。这样,如果将来进行重构或者升级,
我们完全可用不修改线程A,而直接把线程B升级为线程B,
保证系统的平滑过度。而这中间的意见箱就可用使用BlockingQueue来实现。

与之前提到的ConcurrentLinkedQueue类或者CopyOnWriteArrayList类不同,
BlockingQueue是一个接口,并非一个具体的实现。
ArrayBlockingQueue类和LinkedBlockingQueue类。
ArrayBlockingQueue更适合做有界队列,
LinkedBlockingQueue适合做无界队列。
BlockingQueue之所以适合作为数据共享的通道,其关键在于
Blocking上,Blocking是阻塞的意思,但服务线程(服务线程指
不断获取队列中的消息,进行处理的线程)处理完成队列中所有的消息后,
它如何知道下一条消息何时到来呢?
一种简单的做法是让这个线程按照一定的时间间隔不停地循环和监控这个队列。
这是一种可行的方案,但显然造成了不必要的资源浪费。
而且循环周期也难以确定。
BlockingQueue很好地解决了这个问题。它会让服务线程在队列为空时
进行等待,当有新的消息进入队列后,自动将线程唤醒。

### 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 包内的组件们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值