今天项目里面出现了一个bug.原因是在多线程的环境下使用了HashMap。
HashMap是一个非线程安全的类。
举一个例子,在多线程中,如果有当一个线程在遍历HashMap时,另一个线程执行了put或者remove操作会发生ConcurrentModificationException
public class TestHashMap {
public static void main(String[] args) {
final Map<String,String> sessionMap = new HashMap<String,String>();
for(int i=0;i<3;i++){
sessionMap.put(i+ "",i+"" );
}
Thread t = new Thread(new Runnable(){
public void run() {
Iterator<Entry<String, String> iter = sessionMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String,String> entry = (Map.Entry<String,String>)iter.next();
try {
Thread. sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String key = (String)entry.getKey();
String value = (String)entry.getValue();
}
}
});
t.start();
Thread t2 = new Thread(new Runnable(){
public void run() {
try {
Thread. sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sessionMap.remove( "0");
}
});
t2.start();
}
}
static final class Segment<K,V> extends ReentrantLock implements Serializable
执行删除之前的原链表:
执行删除之后的新链表:
从上图可以看出,删除节点 C 之后的所有节点原样保留到新链表中;删除节点 C 之前的每个节点被克隆到新链表中,注意:它们在新链表中的链接顺序被反转了。
在执行 remove 操作时,原始链表并没有被修改,也就是说:读线程不会受同时执行 remove 操作的并发写线程的干扰。
JAVA1.8版本:
java1.8版本中对ConcurrentHashMap做了很大的改变
改进一:取消了单独独立出来的segments字段,而是直接采用Array中每个链表的第一个节点作为锁,从而实现了对每一行数据进行加锁。
以下两行代码提炼自Put()函数
f = tabAt(tab, i = (n - 1) & hash)
synchronized (f)
改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。