java ConcurrentHashMap锁分段技术及原理详解

  

  

<强>线程不安全的HashMap
  因为多线程环境下,使用Hashmap进行把操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用Hashmap。
  

  

<>强效率低下的散列表容器
  

  

HashTable容器使用同步来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用把进行添加元素,线程2不但不能使用把方法添加元素,并且也不能使用得到方法来获取元素,所以竞争越激烈效率越低。

  

<强>锁分段技术
  

  

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如大小()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是最终的,并且其成员变量实际上也是最后的,但是,仅仅是将数组声明为最终的并不保证数组成员也是最后的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

  

癹ava

  

ConcurrentHashMap是由段数组结构和HashEntry数组结构组成.Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个段数组、段的结构和HashMap类似,是一种数组和链表结构,一个片段里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个段守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的段锁。
  

  


  

  

当有一个大数组时需要在多个线程共享时就可以考虑是否把它给分层多个节点了,避免大锁。并可以考虑通过哈希算法进行一些模块定位。
  其实不止用于线程,当设计数据表的事务时(事务某种意义上也是同步机制的体现),可以把一个表看成一个需要同步的数组,如果操作的表数据太多时就可以考虑事务分离了(这也是为什么要避免大表的出现),比如把数据进行字段拆分,水平分表等。

  


  

  

ConcurrentHashMap(1.7及之前)中主要实体类就是三个:ConcurrentHashMap(整个哈希表),段(桶),HashEntry(节点),对应上面的图可以看出之间的关系
  

     /* *   *的片段,每一个都是一个专业的哈希表   */最后Segment[]段;      

<强>不变(不可变的)和易变(挥发性)
  

  

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在哈希链的中间添加或删除元素,读操作不加锁将得到不一致的数据.ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的.HashEntry代表每个散列链中的一个节点,其结构如下所示:
  

        静态最终类HashEntry{   最后K键;   最后int散列;   挥发性V值;   最后HashEntry下一个;   }      

可以看到除不了价值是最终的,其它值都是最终的,这意味着不能从哈希链的中间或尾部添加或删除节点,因为这需要修改下引用值,所有的节点的修改只能从头部开始。对于把操作,可以一律添加到哈希链的头部。但是对于删除操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将值设置成不稳定,这避免了加锁。

  

<强>其它
  

java ConcurrentHashMap锁分段技术及原理详解