多线程(4)线程安全的集合

目录

一、线程安全的集合

二、高效的映射、集合和队列

1、ConcurrentHashMap

2、ConcurrentSkipListMap

三、写数组的拷贝

四、旧的线程安全的集合


一、线程安全的集合

如果多线程要并发地修改一个数据结构,例如散列表,那么很容易会破坏这个数据结构。例如,一个线程可能要开始向表中插入一个新元素。假定在调整散列表各个桶之间的链接关系的过程中,被剥夺了控制权。如果另一个线程也开始遍历同一个链表,可能使用无效的链接并造成混乱,会抛出异常或者陷入死循环。

 可以通过提供锁来保护共享数据结构,但是选择线程安全的实现作为替代可能更容易些。当然,阻塞队列就是线程安全的集合。在下面各小节中,将讨论Java类库提供的另一种线程安全的集合。

二、高效的映射、集合和队列

java.util.concurrent包提供了映像、有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。

这些集合使用复杂的算法,通过允许并发地访问数据结构的不同部分来使竞争极小化。

1、ConcurrentHashMap

ConcurrentHashMap和HashTable都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable的加锁方法是给每个方法加上synchronized关键字,这样锁住的是整个Table对象。而ConcurrentHashMap是更细粒度的加锁。

在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响 。

JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率。

ps:ConcurrentLinkedQueue、ConcurrentLinkedDeque分别是使用单向链表和双向链表实现,原理还是锁分离机制。

关于ConcurrentHashMap更多内容参考:

https://blog.csdn.net/bill_xiang_/article/details/81122044#commentBox

https://baijiahao.baidu.com/s?id=1617089947709260129&wfr=spider&for=pc

2、ConcurrentSkipListMap

ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上能够在O(log(n))时间内完成查找、插入、删除操作。 在非多线程的情况下,应当尽量使用TreeMap。此外对于并发性相对较低的并行程序可以使用Collections.synchronizedSortedMap将TreeMap进行包装,也可以提供较好的效率。对于高并发程序,应当使用ConcurrentSkipListMap,能够提供更高的并发度。同样,ConcurrentSkipListMap支持Map的键值进行排序。

ConcurrentSkipListMap有几个ConcurrentHashMap 不能比拟的优点:   

1、ConcurrentSkipListMap 的key是有序的。

2、ConcurrentSkipListMap 支持更高的并发。ConcurrentSkipListMap 的存取时间是log(N),和线程数几乎无关。也就是说在数据量一定的情况下,并发的线程越多,ConcurrentSkipListMap越能体现出他的优势。

ps:ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。它们俩的关系就像 TreeMap 和TreeSet 。

三、写数组的拷贝

CopyOnWriteArrayList和CopyOnWriteArraySet是线程安全的集合,其中所有的修改线程对底层数组进行复制。如果在集合上进行迭代的线程数超过修改线程数,这样的安排是很有用的。当构建一个迭代器的时候,它包含一个对当前数组的引用。如果数组后来被修改了,迭代器仍然引用旧数组,但是,集合的数组已经被替换了。因而。旧的迭代器拥有一致的(可能过时的)视图,访问它无须任何同步开销。

更多内容参考:https://blog.csdn.net/pentiumchen/article/details/43741237

四、旧的线程安全的集合

从Java的初始版本开始,Vector和Hashtable类就提供了线程安全的动态数组和散列表的实现。在Java SE 1.2中,这些类被弃用了,取而代之的是ArrayList和HashMap类。这些类不是线程安全的,而集合库中提供了不同的机制。任何集合类通过使用同步包装器(synchronized wapper)变成线程安全的:

  List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());

  Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V>());

结果集合的方法使用锁加以保护,提供了线程的安全访问。

 应该确保没有任何线程通过原始的非同步方法访问数据结构。最便利的方法是确保不保存任何指向原始对象的引用,简单地构造一个集合并立即传递给包装器。像我们的例子中所做的那样。

  如果在另一个线程可能进行修改时要对集合进行迭代,仍然需要使用“客户端”锁定

 synchronized (synchHashMap) {

            Iterator<K> iter = synchHashMap.keySet().iterator();

            while (iter.hasHext()) ...;

        }

  如果使用“for each”循环必须使用同样的代码,因为循环使用了迭代器。注意:如果在迭代过程中,别的线程修改集合,迭代器会失效,抛出ConcurrentModifucationException异常。同步仍然是需要的,因此并发的修改可以被可靠地检测出来。

最好使用java.util.concurrent包中定义的集合,不使用同步包装器中的。特别是,假如它们访问的是不同的桶,由于ConcurrentHashMap已经精心地实现了,多线程可以访问它而且不会彼此阻塞。有一个例外是经常被修改的数组列表。在那种情况下,同步的ArrayList可以胜过CopyOnWriteArrayList。

 

参考:《Java核心技术 卷I》

https://www.cnblogs.com/ijavanese/p/3778688.html

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值