导航网站策划,网站举报有奖平台,在线制作图片网站,推广策略英文文章目录 常见集合线程安全性HashMap为什么线程不安全#xff1f;怎么保证HashMap线程安全 HashtableConcurrentHashMap 引入细粒度锁代码中分析总结 小结 常见集合线程安全性
ArrayList、LinkedList、TreeSet、HashSet、HashMap、TreeMap等都是线程不安全的。
HashTable是线… 文章目录 常见集合线程安全性HashMap为什么线程不安全怎么保证HashMap线程安全 HashtableConcurrentHashMap 引入细粒度锁代码中分析总结 小结 常见集合线程安全性
ArrayList、LinkedList、TreeSet、HashSet、HashMap、TreeMap等都是线程不安全的。
HashTable是线程安全的。
HashMap为什么线程不安全
来看个例子
public static void main(String[] args) {HashMapString, Integer map new HashMap();// 创建两个线程同时向HashMap中添加1000个元素Thread thread1 new Thread(new Runnable() {Overridepublic void run() {for (int i 0; i 1000; i) {map.put(String.valueOf(i), i);}}});Thread thread2 new Thread(new Runnable() {Overridepublic void run() {for (int i 1000; i 2000; i) {map.put(String.valueOf(i), i);}}});// 启动线程thread1.start();thread2.start();try {// 等待两个线程执行完成thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 输出 HashMap 的大小System.out.println(map集合大小: map.size());}//输出结果map集合大小:1920这个数字在变动在多线程环境下如果多个线程同时对 HashMap 进行修改操作例如添加、删除元素可能会导致数据结构破坏进而引发各种问题比如丢失数据等。
怎么保证HashMap线程安全
第一 如何保证HashMap的线程安全呢可能你们想到了synchronized ,确实你可以通过在添加元素时使用 synchronized 来确保 HashMap 的线程安全性。这样做可以在多线程环境下保证对 HashMap 的操作是互斥的从而避免了多个线程同时修改 HashMap 导致的线程不安全问题。 Thread thread1 new Thread(new Runnable() {Overridepublic void run() {//synchronized (map) 中的 map 是一个对象锁//它指定了在执行同步代码块时使用的锁对象synchronized (map) {for (int i 0; i 1000; i) {map.put(String.valueOf(i), i);}}}});当一个线程进入同步代码块即 synchronized (map) 所包围的部分时它会尝试获取 map 对象的锁。如果这个锁当前没有被其他线程占用那么该线程将获得锁并可以执行同步代码块中的操作如果该锁已经被其他线程占用那么该线程将被阻塞直到锁被释放。被锁住的对象将会在同步代码块执行完毕后自动释放。
第二 使用Collections.synchronizedMap() 包装 MapInteger, String synchronizedMap Collections.synchronizedMap(new HashMap()); 这样可以获得一个线程安全的 HashMap但性能可能不如 ConcurrentHashMap。
第三 使用 ConcurrentHashMap ConcurrentHashMap 是专门为高并发环境设计的JDK 1.8它使用了 CAS synchronized 来保证线程安全性而且性能表现优秀。 MapInteger, String concurrentHashMap new ConcurrentHashMap(); Hashtable
Hashtable 使用的是全表加锁的方式来保证线程安全也就是说当一个线程要对 Hashtable 进行读写操作时它会对整个 Hashtable 加锁阻塞其他所有线程的访问 HashTable 替代品 ConcurrentHashMap ConcurrentHashMap 引入了细粒度锁和无锁读取等技术大大提高了并发环境下的性能和扩展性 ConcurrentHashMap 引入细粒度锁
ConcurrentHashMap 之所以是线程安全的主要是因为它在内部实现时采用了特殊的机制来确保多个线程同时访问和修改数据时不会发生冲突。
JDK 1.7 版本中的实现 ConcurrentHashMap 在 JDK 1.7 中使用了分段锁Segmentation的结构将整个哈希表分成了多个段Segment每个段有自己的锁。不同段之间的修改操作可以并发进行提高了并发性能只有在同一段内的操作才需要竞争锁。
JDK 1.8 版本中的优化 JDK 1.8 对 ConcurrentHashMap 进行了重大优化废弃了分段锁的设计而是采用了更细粒度的锁分离技术。 在 JDK 1.8 中ConcurrentHashMap 内部使用了基于 CASCompare and Swap 是一种原子操作用于在多线程环境下实现对共享数据的安全更新。CAS 是一种乐观锁机制可以避免使用传统的互斥锁提高了并发性能。 操作的 synchronized 关键字 同时ConcurrentHashMap 在 JDK 1.8 中引入了红黑树作为链表的替代结构当链表长度达到一定阈值时会将链表转换为红黑树以提高查找效率。这种优化又提高了 ConcurrentHashMap 的并发性能和吞吐量。
代码中分析
来看看put方法 public static void main(String[] args) {ConcurrentHashMapString, Integer map new ConcurrentHashMap();map.put(a,111);}在 JDK8 中ConcurrentHashMap 的实现方式已经改变不再采用分段锁的方式而是采用了 CASSynchronized 的方式来保证线程安全
我们需要先了解tabAt ,casTabAt(本质是CAS 算法看下面源码可知)的利用来保障线程安全的操作 tabAt 方法用于从哈希表数组 tab 中获取指定索引 i 处的节点数组。 casTabAt 方法用于原子性地将哈希表数组 tab 中指定索引 i 处的值从 c 更新为 v。(CAS 比较并交换可实现原子操作)
put源码分析: 遍历桶Bin时不加锁 首先代码会根据 hash 值找到对应的桶tab[i]并且如果该桶是空的f null会尝试通过 CAScompare-and-swap 操作将新的节点放入其中这一部分操作是无锁的。 else if ((f tabAt(tab, i (n - 1) hash)) null) {if (casTabAt(tab, i, null, new NodeK,V(hash, key, value, null)))break;
}对非空桶的头节点加锁 当桶 tab[i] 已经存在节点时会进入 synchronized (f) 代码块其中 f 是桶中的头节点。通过对头节点 f 加锁保证对该桶中的修改是线程安全的。 synchronized (f) {if (tabAt(tab, i) f) {// 锁定成功后进行进一步操作}
}这种加锁方式是“桶级别”的锁而不是对整个 HashMap 或 ConcurrentHashMap 加全局锁因此不会影响其他线程对其他桶的访问。 对链表或树结构的操作 加锁后代码会对链表中的节点进行遍历或者在红黑树中进行查找如果桶已经转化为树形结构。在这个过程中同步块只对桶的头节点加锁而不是对每个节点加锁。因此在修改链表或树的过程中保证了线程安全性但并不会影响其他线程对其他桶的操作。 如果该桶是链表结构会遍历链表并进行元素的插入操作 for (NodeK,V e f;; binCount) {K ek;if (e.hash hash ((ek e.key) key || (ek ! null key.equals(ek)))) {oldVal e.val;if (!onlyIfAbsent)e.val value;break;}NodeK,V pred e;if ((e e.next) null) {pred.next new NodeK,V(hash, key, value, null);break;}
}如果该桶是树形结构红黑树则调用 putTreeVal 方法进行树中的插入操作 if (f instanceof TreeBin) {NodeK,V p;binCount 2;if ((p ((TreeBinK,V)f).putTreeVal(hash, key, value)) ! null) {oldVal p.val;if (!onlyIfAbsent)p.val value;}
}加锁粒度 由于只对每个桶的头节点加锁因此即使多个线程同时对不同的桶进行操作也不会相互阻塞。只有在同一个桶中有多个线程试图修改数据时才会发生锁竞争。这种设计提高了并发性能相比全局锁的方式有更高的并发吞吐量。 树化操作 当链表中的节点数量超过 TREEIFY_THRESHOLD 时会将链表转化为红黑树以提高查询和修改效率。树化也是在线程安全的环境下进行的但依然是通过对头节点加锁实现的。 if (binCount TREEIFY_THRESHOLD)treeifyBin(tab, i);总结
加锁机制对每个桶tab[i]的头节点进行加锁。这种锁定策略使得同一个桶中的修改是线程安全的。并发性能优化这种设计避免了全局锁提高了在高并发环境下的性能。当多个线程操作不同的桶时操作不会相互干扰。
小结
而在 JDK 1.8 中ConcurrentHashMap 放弃了分段锁而是采用了更为精细的桶结构。每个桶可以独立加锁使得并发修改操作可以更细粒度地进行。此外当桶中的元素数量达到一定阈值时链表结构会转变为红黑树以减少搜索时间。这种锁分离技术提高了并发性能使得 ConcurrentHashMap 在并发情况下表现更加出色。它是通过 CAS synchronized 来实现线程安全的并且它的锁粒度更小查询性能也更高。 ❤觉得有用的可以留个关注ya❤