有专门做ppt的网站吗,怎么在国外的搜索网站做推广,宁波画册设计,重庆装修网站建设一、什么是ConcurrentHashMap
ConcurrentHashMap和HashMap一样#xff0c;是一个存放键值对的容器。使用hash算法来获取值的地址#xff0c;因此时间复杂度是O(1)。查询非常快。 同时#xff0c;ConcurrentHashMap是线程安全的HashMap。专门用于多线程环境。
二、Concurre…一、什么是ConcurrentHashMap
ConcurrentHashMap和HashMap一样是一个存放键值对的容器。使用hash算法来获取值的地址因此时间复杂度是O(1)。查询非常快。 同时ConcurrentHashMap是线程安全的HashMap。专门用于多线程环境。
二、ConcurrentHashMap和HashMap以及Hashtable的区别
2.1 HashMap
HashMap是线程不安全的因为HashMap中操作都没有加锁因此在多线程环境下会导致数据覆盖之类的问题所以在多线程中使用HashMap是会抛出异常的。
2.2 HashTable
HashTable是线程安全的,但是HashTable只是单纯的在put()方法上加上synchronized。保证插入时阻塞其他线程的插入操作。虽然安全但因为设计简单所以性能低下。
2.3 ConcurrentHashMap
ConcurrentHashMap是线程安全的ConcurrentHashMap并非锁住整个方法而是通过原子操作和局部加锁的方法保证了多线程的线程安全且尽可能减少了性能损耗。
由此可见HashTable可真是一无是处…
三、ConcurrentHashMap原理
这一节专门介绍ConcurrentHashMap是如何保证线程安全的。如果想详细了解ConcurrentHashMap的数据结构请参考HashMap。
3.1 volatile修饰的节点数组
请看源码
//ConcurrentHashMap使用volatile修饰节点数组保证其可见性禁止指令重排。
transient volatile NodeK,V[] table;再看看HashMap是怎么做的
//HashMap没有用volatile修饰节点数组。
transient NodeK,V[] table;显然HashMap并不是为多线程环境设计的。
3.2 ConcurrentHashMap的put()方法
//put()方法直接调用putVal()方法
public V put(K key, V value) {return putVal(key, value, false);
}
//所以直接看putVal()方法。
final V putVal(K key, V value, boolean onlyIfAbsent) {if (key null || value null) throw new NullPointerException();int hash spread(key.hashCode());int binCount 0;for (NodeK,V[] tab table;;) {NodeK,V f; int n, i, fh;if (tab null || (n tab.length) 0)tab initTable();else if ((f tabAt(tab, i (n - 1) hash)) null) {if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null)))break; // no lock when adding to empty bin}else if ((fh f.hash) MOVED)tab helpTransfer(tab, f);else {V oldVal null;synchronized (f) {if (tabAt(tab, i) f) {if (fh 0) {binCount 1;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;}}}else 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;}}}}if (binCount ! 0) {if (binCount TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal ! null)return oldVal;break;}}}addCount(1L, binCount);return null;
}我来给大家讲解一下步骤把。
public V put(K key, V value) {首先put()方法是没有用synchronized修饰的。
for (NodeK,V[] tab table;;)新插入一个节点时首先会进入一个死循环 情商高的就会说这是一个乐观锁 进入乐观锁后
if (tab null || (n tab.length) 0)tab initTable();如果tab未被初始化则先将tab初始化。此时这轮循环结束因为被乐观锁锁住开始下一轮循环。 第二轮循环此时tab已经被初始化了所以跳过。
else if ((f tabAt(tab, i (n - 1) hash)) null) {if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null)))break; // no lock when adding to empty bin
}接下来通过key的hash值来判断table中是否存在相同的key如果不存在执行casTabAt()方法。 注意这个操作时不加锁的看到里面的那行注释了么// no lock when adding to empty bin。位置为空时不加锁。 这里其实是利用了一个CAS操作。
CAS(Compare-And-Swap)比较并交换
这里就插播一个小知识CAS就是通过一个原子操作用预期值去和实际值做对比如果实际值和预期相同则做更新操作。 如果预期值和实际不同我们就认为其他线程更新了这个值此时不做更新操作。 而且这整个流程是原子性的所以只要实际值和预期值相同就能保证这次更新不会被其他线程影响。
好了我们继续。 既然这里用了CAS操作去更新值那么就存在两者情况。
实际值和预期值相同 相同时直接将值插入因为此时是线程安全的。好了这时插入操作完成。使用break;跳出了乐观锁。循环结束。实际值和预期值不同 不同时不进行操作因为此时这个值已经被其他线程修改过了此时这轮操作就结束了因为还被乐观锁锁住进入第三轮循环。
第三轮循环中前面的判断又会重新执行一次我就跳过不说了进入后面的判断。 else if ((fh f.hash) MOVED)tab helpTransfer(tab, f);这里判断的是tab的状态MOVED表示在扩容中如果在扩容中帮助其扩容。帮助完了后就会进行第四轮循环。 终于来到了最后一轮循环。
else {V oldVal null;synchronized (f) {if (tabAt(tab, i) f) {if (fh 0) {binCount 1;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;}}}else 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;}}}}if (binCount ! 0) {if (binCount TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal ! null)return oldVal;break;}
}上面的判断都不满足时就会进入最后的分支这条分支表示key的hash值位置不为null(之前的判断是hash值为null时直接做插入操作)表示发生了hash冲突此时节点就要通过链表的形式存储这个插入的新值。Node类是有next字段的用来指向链表的下一个位置新节点就往这插。
synchronized (f) {看终于加排它锁了只有在发生hash冲突的时候才加了排它锁。
if (tabAt(tab, i) f) {if (fh 0) {重新判断当前节点是不是第二轮判断过的节点如果不是表示节点被其他线程改过了进入下一轮循环 如果是再次判断是否在扩容中如果是进入下一轮循环 如果不是其他线程没改过继续走
for (NodeK,V e f;; binCount) {for循环循环遍历这个节点上的链表
if (e.hash hash ((ek e.key) key ||(ek ! null key.equals(ek)))) {oldVal e.val;if (!onlyIfAbsent)e.val value;break;
}找到一个hash值相同且key也完全相同的节点更新这个节点。 如果找不到
if ((e e.next) null) {pred.next new NodeK,V(hash, key,value, null);break;
}往链表最后插入这个新节点。因为在排他锁中这些操作都可以直接操作。终于到这插入就基本完成了。
总结
做插入操作时首先进入乐观锁 然后在乐观锁中判断容器是否初始化 如果没初始化则初始化容器 如果已经初始化则判断该hash位置的节点是否为空如果为空则通过CAS操作进行插入。 如果该节点不为空再判断容器是否在扩容中如果在扩容则帮助其扩容。 如果没有扩容则进行最后一步先加锁然后找到hash值相同的那个节点(hash冲突) 循环判断这个节点上的链表决定做覆盖操作还是插入操作。 循环结束插入完毕。
3.3 ConcurrentHashMap的get()方法
//ConcurrentHashMap的get()方法是不加锁的方法内部也没加锁。
public V get(Object key)看上面这代码ConcurrentHashMap的get()方法是不加锁的为什么可以不加锁因为table有volatile关键字修饰保证每次获取值都是最新的。
//Hashtable的get()是加锁的所以性能差。
public synchronized V get(Object key) 再看看Hashtable差距啊。
四、使用场景
嗯多线程环境下更新少查询多时使用的话性能比较高。 乐观锁嘛认为更新操作时不会被其他线程影响。所以时候再更新少的情况下性能高。
对你有帮助吗点个赞吧~