由于并发程序和串行程序的不同特点,在串行程序中使用的数据结构可能无法在并行程序中直接的正常使用,因为这些数据结构可能不是线程安全的,所以这一次并发程序的优化介绍的是并发程序中的数据结构,比如并发List,并发Set,并发Map等。
1.并发List
Vector实现的List接口,CopyOnWriteArrayList也是实现的List接口,这两个List的实现是线程安全的,但是对于ArrayList不是线程安全的,所以在多线程中尽量避免使用ArrayList,如果因为某些原因必须使用的话,需呀使用同步机制:
Collection.synchronizedList(List list)对List进行封装,封装成为线程安全的List。
CopyOnWriteArrayList:实现的机制与Vector的机制是不同的,当对对象进行写操作的时候,复制该对象;如果是进行读操作的话,那么就直接返回结果,不需要同步;核心思想就是减少所得竞争,从而提高程序的性能,但是这个数据结构在一定程度上牺牲了写的性能。
Vector的实现使用的是同步机制,get所有的操作必须是首先获得对象的锁才可以进行,在高并发的情况支架,锁机制会拖累系统性能。
Vector适合写操作,CopyOnWriteArrrayList适合的是读操作并发程序
2.并发Set
类似List,Set 也有一个CopyOnWriteArraySet,而且是线程安全的,内部实现完全依赖于CopyOnWriteArrayList,适合使用在读操作多的并发场景,当然也可以使用的是
Collections中的静态方法将数据结构转换成为线程安全的。
public static <T> Set<T> synchronizedSet(Set<T> s);
3.并发Map
同样可以使用Collectionsz中的synchornizedMap()方法转换,但是性能比较差,JDK中提供了一个比较高效的Map数据结构:ConcurrrentHashMap.他的get是无锁的,他的put的锁粒度是比较小的,所以效率会比较高。
4.并发Queue
JDK中有两种代表性的实现:ConcurrentLinkedQueue为代表的高性能队列和BlockingQueue为接口的阻塞队列,都是集成Queue接口。
ConcurrentLinkedQueue适合的是高并发场景的队列,通过无锁的方式实现的,而且性能是高于BlockingQueue。
5.并发Deque
在JDK中实现了双端队列,可以在头部和尾部实现出队和入队,他有三个实现类:LinkedList,ArrayDeque, LinkedBlockingDeque。它们都实现了双端队列的接口 Deque。ArrayDeque是基于Array实现的,拥有高效的随机访问性质,但是当队列变大的时候,就需要重新分配内存空间,并且才需要复制数值;所以LinkedList会表现出更好的性能。以上两者都不是线程安全的。
需要使用LinkedBlockingDeque,这是一个线程安全的Deque的实现,他没有实现读写锁的分离,所以高并发系统中性能是地狱LinkedBlockingQueue的。
- 关于 Collections.synchronizedList(List) 等一系列构建出来线程安全的List、Set、Map均为包装了一个基本的List、Set、Map上的读写操作方法上增加了synchronized锁(不管get、put、add等操作均加上了synchronized)
- 而Jdk的java.util.concurrent提供了高性能的一系列的线程安全的数据结构
常用线程安全的数据结构 :
- CopyOnWriteArrayList : get方法无锁,add方法:每次都copy出1个副本,并且利用ReentrantLock加上了锁,所以add方法的性能是比较低的
- public boolean add(E e) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Object[] elements = getArray();
- int len = elements.length;
- //这里会进行数组的整体复制
- <strong><span style="color: #ff0000;">Object[] newElements = Arrays.copyOf(elements, len + 1);</span></strong>
- //修改副本
- newElements[len] = e;
- //将副本设置回来
- setArray(newElements);
- return true;
- } finally {
- //释放锁
- lock.unlock();
- }
- }
- CopyOnWriteArraySet : 内部实现完全依赖于CopyOnWriteArrayList
- ConcurrentHashMap : 专门为线程并发而设计的HashMap,get()操作是无锁的,而put()操作的锁粒度又小于synchronized的HashMap,因此整体性能优于synchronized的HashMap。
- 并发Queue: JDK提供2套实现(两着都继承自Queue接口):
(1) : ConcurrentLinkedQueue为代表的高性能队列
场景: 适用于高并发场景下的队列,通过无锁的方式实现高并发下的高性能,ConcurrentLinkedQueue的性能要好于BlockingQueue。
(2) : BlockingQueue接口为代表的阻塞队列;
场景:BlockingQueue并不是在于提升高并发时的队列性能,而是在于简化多线程间的数据共享。主要用于生产者-消费者模式中。读写阻塞。主要有ArrayBlockingQueue和LinkedBlockingQueue。
5. 并发Deque(双端队列): 具体可以参考JDK源码。LinkedList,ArrayDeque,LinedBlockingDeque均实现了Deque接口。