HashMap 多线程push造成死锁

虽然一直都知道HashMap是非线程安全的,但是直到真的踏入了坑才能有深刻认识。

死锁代码

locks[(int) (id % LOCK_NUM)].lock();//ReentrantLock.lock();
rowData = dataBase.get(id);             //HashMap.get(key);
if (rowData == null) {
    rowData = new RowData();
    dataBase.put(id, rowData);
}
locks[(int) (id % LOCK_NUM)].unlock();

JConsole查看

调试了一两个小时,主要是因为一直认为是ReentrantLock造成的死锁。正纳闷完全不符合死锁条件,怎么就会死锁。关键是这个死锁大概以30%的概率出现,^_^,感觉好幸运,要是3%的概率出现我就歇菜了。逗逼地F8了好久,直到记起来用JConsole查看死锁状态,发现结果如下。

Name: Thread-0
State: WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@4097d66a owned by: Thread-2
Total blocked: 0  Total waited: 12

Name: Thread-1
State: WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@4097d66a owned by: Thread-2
Total blocked: 0  Total waited: 6

Name: Thread-2
State: RUNNABLE
Total blocked: 1  Total waited: 12

Stack trace: 
java.util.HashMap.getEntry(HashMap.java:465)
java.util.HashMap.get(HashMap.java:417) 

死锁原因

有了线程栈,问题就很easy了,直接去HashMap.java看下发现问题 :为什么会产生循环链表参见这篇文章《HashMap死锁分析》
补充:自己看上面的这篇文章觉得篇幅略长。决定自己来写点篇幅简单的。

for (Entry e : table) {
     while(null != e) {
        Entry next = e.next;
         if (rehash) {
             e.hash = null == e.key ? 0 : hash(e.key);
         }
         int i = indexFor(e.hash, newCapacity);
         e.next = newTable[i];
         newTable[i] = e;
         e = next;
     }
 }

假设有hash表hash[0] = e(3,7,null);//e表示链表;
线程1和线程2同时resize。
线程1刚执行完Entry next = e.next;
所以next = 7, e = 3;
线程2完成了rehash,所以当前链表尾hash[0] = e(7,3,null);//第一次逆转;
剩下都是线程1:

  1. e = 3,next = 7; //线程2resize之前获得的,所以3指向7
  2. e.next = newTable[i] ;newTable[i] = e; //hash[0] = e(3,null);
  3. e = next ;// e= 7,e.next = 3;
  4. e = 7,next = 3; //线程2resize之后获得的,所以7指向3
  5. e.next = newTable[i] ;newTable[i] = e; //hash[0] = e(7,3,null);
  6. e = next; // e = 3, e.next = null;
  7. e = 3, next = null;
  8. e.next = newTable[i] ;newTable[i] = e; //hash[0] = e(3,7,3); 循环链表。

    简而言之就是如果Thread-1和Thread-2同时push并且需要Resize当前HashMap,由于并发它们不知道对方也在Resize,而Reasize过程中会对每一个Entry进行逆转,相当于两个Thread同时逆转一个单链表。所以在并发时如果不能保证Push操作的原子性,应该使用ConcunrentHashMap 。

final Entry getEntry(Object key) {
if (size == 0) {
            return null;
        }

   int hash = (key == null) ? 0 : hash(key);
   for (Entry e = table[indexFor(hash, table.length)]; 
            e != null;
            e = e.next) {                 //465 多线程push时使e成为了循环链表,get一个不在其中的key会导致死循环
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

你可能感兴趣的:(java)