那些年应该相识的线程安全集合们
- 2019 年 10 月 6 日
- 筆記
上篇推文介绍了List的三种实现其实都不是线程安全的,文章结尾也回答了如何创建线程安全的List,答案是:Collections.synchronizedList。
接下来小强再后台收到热心童鞋(在此特别感谢并艾特三老师)回复了其他方式List线程安全的实现方式:CopyOnWriteArrayList。
本文就为大家总结下Concurrent下常用的线程安全集合们。主要包含以下两种类型的集合:
– CopyOnWriteArrayList
– BlockingQueue/BlockingDeque
CopyOnWriteArryList
在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的:
public void add(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len); Object[] newElements; int numMoved = len - index; if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
传统的List在多线程同时读写的时候会抛出java.util.ConcurrentModificationException,而CopyOnWriteArrayList是使用CopyOnWrite(写时复制)技术解决了这个问题,上面代码是向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList里添加元素),可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。
读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList
public E get(int index) { return get(getArray(), index); }
所以,通过上面的介绍,CopyOnWriteArrayList并发场景更适合于读多写少的场景。
BlokingQueue/BlockingDeque
阻塞队列BlockingQueue是一个接口,BlockingDeque继承了BlockingQueue,代码如下:
public interface BlockingQueue<E> extends Queue<E> { }
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> { }
这里两个比较相似的阻塞队列,BlockingQueue和BlockingDeque,两个都是队列,只不过前者只能一端出一端入,后者则可以两端同时出入,并且他们的实现类都是结构改变线程安全的队列。
BlockingQueue接口的常用实现有以下几种:
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
BlockingDeque接口的常用实现有以下几种:
- LinkedBlockingDeque
其实两个队列从实现思想上比较容易理解,有以下特点:
- 链表结构(动态数组)
- 通过ReentrantLock实现锁
- 利用Condition实现队列的阻塞等待,唤醒
下面以ArrayBlockingQueue的构造方法举例来了解以上特点:
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
结语
其实还有一种很常见的线程安全结合ConcurrentHashMap。它是一种支持高并发、高吞吐量的线程安全HashMap实现,其中,在java8中对ConcurrentHashMap的结构进行了很大的改造。
以后的文章,小强可以详细介绍这部分内容,因为这部分属于JAVA面试中非常常见的问题,需要专门的篇幅来介绍。在此不赘述。