由于并行程序与串行程序的不同特点,适用于串行程序当中的常用数据结构在并发环境中会引发线程安全问题,例如ArrayList,HashSet,HashMap等,这是因为这些数据结构不是线程安全的,所以编写并行程序时需要将原串行数据结构转换为线程安全或使用对并行程序效率更高的并行数据结构
使传统集合更改为线程安全集合
Java代码
- public class TestMain {
- @Test
- public void testTraditionCollections() throws Exception {
- // Collections中提供多种方法将集合变成线程安全
- // 转换ArrayList
- Collections.synchronizedList(new ArrayList<Object>());
- // 转换HashMap
- Collections.synchronizedMap(new HashMap<Object, Object>());
- // 转换HashSet
- Collections.synchronizedSet(new HashSet<Object>());
- }
并发List
Vector和CopyOnWriteArrayList是两个线程安全的List实现,而Vector和CopyOnWriteArrayList的内部实现又有所不同,Vector使用锁机制实现线程安全,在多线程世界中锁是并发的最大敌人,太多的锁竞争会消耗系统资源和降低并发,而CopyOnWriteArrayList实现并不使用锁,而是使用了对象的不变性,在对象读取时不需要加锁,而是在试图改变对象时,先获取对象的一个copy,对copy进行修改后将副本回写,减少了锁竞争提高了并发时的读取性能,但某程度上牺牲了写的性能,当写操作频繁时请选择Vector,当高并发读取时,应该选择CopyOnWriteArrayList
并发Set
对比上述并发List,Set当中亦存在CopyOnWriteArraySet,对应并发List当中的CopyOnWriteArrayList,而Vector对应使用Collections.synchronizedSet后的Set
并发Map
在并发环境中使用Collections.synchronizedMap可以获取线程安全的Map,或HashTable,但JDK提供了另一种并发ConcurrentHashMap,ConcurrentHashMap的读写速度比同步Map速度更快,ConcurrentHashMap如此高效得益于它的get操作是无锁的,而put操作锁的粒度比同步的Map小,在高并发环境中务必优先选择ConcurrentHashMap
并发Queue
在并发Queue中提供了两套具有代表性的实现,分别是高性能的ConcurrentLinkedQueue和BlockingQueue,ConcurrentLinkedQueue适用于高并发的读写,它通过无锁的方式实现高性能,BlockingQueue的主要功能并不是体现在提升并发时队列的性能,而在于简化多线程之间的数据共享,阻塞队列在生产者-消费者模式中得到了完美的体现,BlockingQueue提供了3种存取方式,请读者参考JDK文档
PS:BlockingQueue有ArrayBlockingQueue和LinkedBlockingQueue两个实现类,相信读者已熟悉Array和Linked的区别,此乃经典面试题,笔者在此不再叙述
并发Deque
Deque是基于链接点的阻塞栓双端队列,Deque允许在队列的头部或者尾部进行读写,LinkedList也实现了Deque接口,Deque和Queue一样提供了3种元素的存取方式,甚至提供了指读取不删除的操作方式,详情请读者查看JDK帮助文档,但LinkedBlockingDeque并没有进行读写锁分离,因此在效率方面要低于LinkedBlockingQueue更远低于ConcurrentLinkedQueue
总结:
软件中数据结构博大精深,但万变不离经典,除了要熟练使用传统串行数据结构外,还需要对并发的数据结构有所认识和了解,特别在开发并行程序时需要格外注意,否则将出现非常难以重现的错误,在并发数据结构中还有Apache下的Amino框架,Amino框架提供更快速实现CAS算法的数据结构,详情请留意笔者后续文章