这里“通话”和“重地”的哈希值是一样的,那么他们添加时,会存入同一个Segment对象,必然会存在锁竞争
public static void main(String[] args) throws Exception { final ConcurrentHashMap chm = new ConcurrentHashMap(); new Thread(){ @Override public void run() { chm.put("通话","11"); System.out.println("-----------"); } }.start(); //让第一个线程先启动,进入put方法 Thread.sleep(1000); new Thread(){ @Override public void run() { chm.put("重地","22"); System.out.println("==========="); } }.start(); }断点设置
运行结果
会发现两个线程,分别停在不同的断点位置,这就是多线程锁互斥产生的结果
然后就可以分别让不同的线程向下执行,查看代码走向了。
源码分析
private void rehash(HashEntry<K,V> node) { HashEntry<K,V>[] oldTable = table; int oldCapacity = oldTable.length; //两倍容量 int newCapacity = oldCapacity << 1; threshold = (int)(newCapacity * loadFactor); //基于新容量,创建HashEntry数组 HashEntry<K,V>[] newTable = (HashEntry<K,V>[]) new HashEntry[newCapacity]; int sizeMask = newCapacity - 1; //实现数据迁移 for (int i = 0; i < oldCapacity ; i++) { HashEntry<K,V> e = oldTable[i]; if (e != null) { HashEntry<K,V> next = e.next; int idx = e.hash & sizeMask; if (next == null) // Single node on list //原位置只有一个元素,直接放到新数组即可 newTable[idx] = e; else { // Reuse consecutive sequence at same slot //=========图一===================== HashEntry<K,V> lastRun = e; int lastIdx = idx; for (HashEntry<K,V> last = next; last != null; last = last.next) { int k = last.hash & sizeMask; if (k != lastIdx) { lastIdx = k; lastRun = last; } } //=========图一===================== //=========图二===================== newTable[lastIdx] = lastRun; //=========图二===================== // Clone remaining nodes //=========图三===================== for (HashEntry<K,V> p = e; p != lastRun; p = p.next) { V v = p.value; int h = p.hash; int k = h & sizeMask; HashEntry<K,V> n = newTable[k]; //这里旧的HashEntry不会放到新数组 //而是基于原来的数据创建了一个新的HashEntry对象,放入新数组 newTable[k] = new HashEntry<K,V>(h, p.key, v, n); } //=========图三===================== } } } //采用头插法,将新元素加入到数组中 int nodeIndex = node.hash & sizeMask; // add the new node node.setNext(newTable[nodeIndex]); newTable[nodeIndex] = node; table = newTable; }图一
图二
图三
在jdk8的ConcurrentHashMap中一共有5个构造方法,这四个构造方法中都没有对内部的数组做初始化, 只是对一些变量的初始值做了处理