并发容器-J.U.C之线程安全集合

copyOnWriteArrayList

线程写操作时复制,当有新元素添加到copyOnWriteArrayList时,它先从原有的数组中拷贝出一份,在新开辟出的新数组中写入,写完后再将原数组指向新数组。其操作都是在锁的域中,防止在多线程中复制出多个副本出来,导致原数组指向错误。

特点:

  1. 由于写操作需进行复制操作,耗用内存;当元素内容过多时,该复制操作会占用非常多的内存,导致minor-GC,甚至full-GC。
  2. 虽然最终会保持一致性,但不能用于实时读的操作。
  3. 读写分离,且适合读多写少的场景。
  4. 若add或set的数据不清楚或过多,要慎用!
  5. 读时不加锁,写时加锁。

测试例子:(还是原来的测试框架,测试实例变量声明如下)

1
private static List<Integer> list = new CopyOnWriteArrayList<>();

运行结果:

1
22:03:09.363 [main] INFO com.mmall.concurrency.concurrent.CopyOnWriteArrayListExample - size:5000

结果验证了该类为线程安全的!

看一下它的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();

public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

显然是使用重入锁进行操作加锁。add方法的实现与上面的说明对应!

copyOnWriteArraySet

对应于HashSet。

它的底层实现与copyOnWriteArrayList是一样的。特点也是一样的。当使用迭代器iterator迭代时,速度快效率高线程安全。

ConcurrentSkipListSet

对应于TreeSet。是JDK 1.6中新增的类,同样支持自然排序。在构造时可以自定义比较器。基于map集合,故而多线程并发环境下,它的类内插入、移除、访问方法都是线程安全的。但是对于批量操作,比如addAll()、removeAll()、retainAll()、containsAll并不能保证其操作的原子性。

例子就不用演示了,跟上面的例子方法几乎完全一样,运行结果也显示是线程安全的!批量操作时就不能保证线程安全了!需额外增加锁机制。

ConcurrentHashMap

对应于HashMap。其中需注意,key或Value不需为null。高并发环境中,表现较好。(后续会详细讲)

ConcurrentSkipListMap

对应于TreeMap。内部是使用SkipList即跳表的结构实现。

SkipList: 跳跃链表是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间),并且对并发算法友好。基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机化的方式进行的,所以在列表中的查找可以快速的跳过部分列表(因此得名)。 推荐阅读

跳表性质:

  1. 由很多层结构组成;
  2. 每一层都是一个有序的链表;
  3. 最底层(Level 1)的链表包含所有元素;
  4. 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现;
  5. 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
    推荐阅读

虽然ConcurrentSkipListMap的效率不及ConcurrentHashMap,但它也有ConcurrentHashMap不可比拟的优点:

  1. ConcurrentSkipListMap中的key值是有序的。
  2. 支持更高的线程并发。其存取时间与线程数量是几乎没有关系的。即线程越多,越有利于ConcurrentSkipListMap的性能发挥。

上面介绍的几个例子就不进行例子演示了,均只是改变了变量的声明,其他内容仍然与原测试结构相同。

SupriseMF wechat
欢迎关注微信订阅号【星球码】,分享学习编程奇淫巧技~
喜欢就支持我呀(*^∇^*)~

热评文章