Java ConcurrentHashMap分析

原创 2015年11月17日 20:38:31

今天项目里面出现了一个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();
    }
}


我们知道可以用线程安全的Hashtable来代替HashMap。但问题是Hashtable的做法是在所有的方法前都加入了synchronized关键字来实现同步
这样效率是很低的,因为所有线程对这个map的任何操作都要竞争这个锁

所以这个时候ConcurrentHashMap就是很好的替代了
首先还是之前的例子 把HashMap换成ConcurrentHashMap后代码可以很好的运行不会有任何错误
那么HashMap是如何实现同步,而又比Hashtable高效呢

JAVA1.7版本:

一。ConcurrentHashMap把整个Hash表切割成了很多块Segment)。
每一个块用一个重入锁保证它的线程安全性。
static final class Segment<K,V> extends ReentrantLock implements Serializable
每一个Segment管理一个hash表
当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
那么具体的的结构如下:


二。ConcurrentHashMap的get()操作是无锁的而且能保证线程一致性。
ConcurrentHashMap中Entry的value值是用volatile关键字修饰的
定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值。之所以不会读到过期的值,是根据java内存模型的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get() 操作也能拿到最新的值,这是用volatile替换锁的经典应用场景。——深入分析ConcurrentHashMap 

三。对ConcurrentHashMap进行遍历的时候。
期间其他线程对这个ConcurrentHashMap做了修改(比如执行remove或者put)不会出现错误。
ConcurrentHashMap 中的每一个Entry的hash值,key值,next指针(指向下一个Entry)都是是final关键字定义的。这就保证了一旦通过put()操作往hash表里面插入了一个Entry以后,就无法把它从链表上删除了。

那remove()操作怎么办呢?
它的办法就是——创建一个新的链表。
当我们执行remove()操作的时候。把待删除节点之后的所有节点原样保留在新链表中,把待删除节点之前的每个节点克隆到新链表中。下面通过图例来说明remove()操作假设写线程执行 remove() 操作,要删除链表的 C 节点,另一个读线程同时正在遍历这个链表。

执行删除之前的原链表:
图 4. 执行删除之前的原链表:
执行删除之后的新链表:
图 5. 执行删除之后的新链表

——摘自:探索 ConcurrentHashMap 高并发性的实现机制

从上图可以看出,删除节点 C 之后的所有节点原样保留到新链表中;删除节点 C 之前的每个节点被克隆到新链表中,注意:它们在新链表中的链接顺序被反转了

在执行 remove 操作时,原始链表并没有被修改,也就是说:读线程不会受同时执行 remove 操作的并发写线程的干扰。

综合上面的分析我们可以看出,写线程对某个链表的结构性修改不会影响其他的并发读线程对这个链表的遍历访问。

参考:
jdk1.7源码
《java并发编程实战》
深入分析ConcurrentHashMap 

再谈重入锁


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),可以改进性能。



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

《Java源码分析》:ConcurrentHashMap JDK1.8

《Java源码分析》:ConcurrentHashMap JDK1.8最近一直在看关于J.U.C中的源码,了解原子操作,了解锁机制,了解多线程并发等等。但是ConcurrentHashMap一直拖着...

Java集合---ConcurrentHashMap原理分析

集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。这篇文章主...

Java ConcurrentHashMap 源代码分析

之前分析过HashMap,Hashtable的源代码,前者不支持多线程环境,但是如果使用了的话,在一定情况下会导致死锁,以后会分析。后者支持并发,但是每次只能有一个线程来操作。也就是说,这个锁是锁定了...

Java源码分析:ConcurrentHashMap

上篇文章和大家分享了HashMap的源码,了解了HashMap的原理。和HashMap比较相似的有Hashtable,Hashtable的代码和HashMap大部分相似,关键的区别在于前者是线程安全的...

Java 8 中的ConcurrentHashMap源码分析

在HashMap的分析中,介绍了hashmap不是线程安全的,其在并发环境使用fail-fast策略来抛出由并发错误导致的异常。 先来看下Hashtable这个线程安全的容器,其虽然是线程安全的...

【Java并发编程】深入分析ConcurrentHashMap(九)

本章是提高教程可能对于刚入门同学来说会有些难度,读懂本章你需要了解以下知识点:一、 【Java基础提高】深入分析final关键字(一)二、 【Java并发编程】深入分析volatile(四)三、 【J...

Java集合-----ConcurrentHashMap原理分析

转载:http://www.cnblogs.com/ITtangtang/p/3948786.html    集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如...

Java多线程 -- JUC包源码分析6 -- ConcurrentHashMap

ConcurrentHashMap的源码,从JDK1.6到JDK1.7, 经历了不少变化。在JDK1.7中,好几个地方使用了sun.misc.Unsafe里面的函数,比如UNSAFE.putOrde...

Java 中 ConcurrentHashMap 原理分析

一.Java并发基础 当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题。 在一个对象中有一个变量i=0,有两个线程A,B都想对i加1,这个时候便有问题显现出来,关键就是对...

Java8—ConcurrentHashMap分析

前言ConcurrentHashMap在JDK8中进行了巨大改动,很需要通过源码来再次学习下Doug Lea的实现方法。它摒弃了java7中Segment(锁段)的概念,而是启用了一种全新的方式实现,...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)