前言
在 JUC 中有几个重要的并发集合,这里简单记录一下对它们的认识。
CopyOnWriteArrayList
这个容器是为了ArrayList线程不安全设计的。我们都知道在java容器中ArrayList是线程不安全的,而vector是线程安全的。那么针对线程安全和不安全来说,这两个容器应该是够用了,为什么还要出现一个CopyOnWriteArrayList这个容器呢?CopyOnWriteArrayList是线程安全的,那么它较vector有哪些优势呢?看一下vector的add和get源码:
1 | public synchronized boolean add(E e) { |
可以看到对于vector的add和get方法都被synchronize修饰了,这就说明了当我在读取数据的时候,其他线程也是不能获取的,所有的读和写都会被阻塞,性能太差了。
再来看CopyOnWriteArrayList中的源码:
1 | public boolean add(E e) { |
首先看add的方法。简单的概括就是,在添加数据的时候,系统会复制一个新的Array,然后添加的时候往新的Array添加,当然这个新的Array由Lock确保其add线程安全,注意原来的Array没有加锁,get读取的是旧的Array,所以可以保证get不会同步。再来看get方法:
1 | public E get(int index) { |
get操作是没有加锁的,所以性能会很快。copyOnWriterArrayList通过消耗空间的方式来提高系统的并发性,但是这样浪费了空间,容易造成minor Gc ,严重的话会造成full gc。所以要根据情况来选择使用。
ConcurrentHashMap
同样的ConcurrentHashMap是为了解决HashMap线程不安全的问题,但是你们有会疑惑,HashTable不是已经解决了HashMap线程不安全的问题了吗?为什么还需要ConcurrentHashMap呢?先来看一下HashTable的get和put操作:
1 | public synchronized V put(K key, V value) { |
Hashtable的synchronized是针对整张Hash表的,即每次读写都是锁住整张表让线程独占,这个与Vector有点像,性能太差。而ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。