Java源码学习笔记

"Java并发编程ConcurrentHashMap"

Posted by jhljx on July 23, 2016

目录

1. Java ConcurrentHashMap

HashMap未考虑同步,HashTable考虑了同步,带来的直接影响是可选择。 单线程时使用HashMap提高效率,多线程使用HashTable保证线程安全。

HashTable的锁粒度太大,针对整张Hash表上锁,浪费严重。因此提出了ConcurrentHashMap。ConcurrentHashMap的锁粒度是Hash表中的一个桶。

ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。试想,原来只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制,之后会提到),并发性的提升是显而易见的。

更令人惊讶的是ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操作时才需要锁定整个表。

This map usually acts as a binned (bucketed) hash table.

ConcurrenHashMap中使用到锁(ReentrantLock),Volatile,final等手段来保证happens-before规则的。

在Java5之后,增加了一些其它锁,比如ReentrantLock,它基本作用和synchronized相似,但提供了更多的操作方式,比如在获取锁时不必像synchronized那样只是傻等,可以设置定时,轮询,或者中断,这些方法使得它在获取多个锁的情况可以避免死锁操作。

在ConcurrentHashMap中,每个hash区间使用的锁正是ReentrantLock。

Volatile可以看做一种轻量级的锁,但又和锁有些不同。 a) 它对于多线程,不是一种互斥(mutex)关系。 b) 用volatile修饰的变量,不能保证该变量状态的改变对于其他线程来说是一种“原子化操作”。

在Java5之前,JMM对Volatile的定义是:保证读写volatile都直接发生在main memory中,线程的working memory不进行缓存。它只承诺了读和写过程的可见性,并没有对Reording做限制,所以旧的Volatile并不太可靠。在Java5之后,JMM对volatile的语义进行了增强。就是我们看到的③ volatile变量法则。

那对于“原子化操作”怎么理解呢?看下面例子:

private static volatile int nextSerialNum = 0;
 
public static int generateSerialNumber(){
    return nextSerialNum++;
}

上面代码中对nextSerialNum使用了volatile来修饰,根据前面“Happens-Before”法则的第三条Volatile变量法则,看似不同线程都会得到一个新的serialNumber。

问题出在了 nextSerialNum++ 这条语句上,它不是一个原子化的,实际上是read-modify-write三项操作,这就有可能使得在线程1在write之前,线程2也访问到了nextSerialNum,造成了线程1和线程2得到一样的serialNumber。 所以,在使用Volatile时,需要注意:
a) 需不需要互斥;
b) 对象状态的改变是不是原子化的。

不变模式(immutable)是多线程安全里最简单的一种保障方式。因为你拿他没有办法,想改变它也没有机会。 不变模式主要通过final关键字来限定的。在JMM中final关键字还有特殊的语义。Final域使得确保初始化安全性(initialization safety)成为可能,初始化安全性让不可变形对象不需要同步就能自由地被访问和共享。

当有一个大数组时需要在多个线程共享时就可以考虑是否把它给分层多个节点了,避免大锁。并可以考虑通过hash算法进行一些模块定位。

其实不止用于线程,当设计数据表的事务时(事务某种意义上也是同步机制的体现),可以把一个表看成一个需要同步的数组,如果操作的表数据太多时就可以考虑事务分离了(这也是为什么要避免大表的出现),比如把数据进行字段拆分,水平分表等.

为了加快定位段以及段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。当并发级别为默认值16时,也就是段的个数,hash值的高4位决定分配在哪个段中。但是我们也不要忘记《算法导论》给我们的教训:hash槽的的个数不应该是 2^n,这可能导致hash槽分配不均,这需要对hash值重新再hash一次。(这段似乎有点多余了 )

看算法导论,hash到底怎么搞==

在concurrenthashmap中get函数中value==null再次get一次不理解。。

Java null讲解 http://www.importnew.com/14229.html

http://blog.csdn.net/liuzhengkang/article/details/2916620

http://www.ibm.com/developerworks/cn/java/j-jtp07233/

Java并发:reorder重排序 http://ifeve.com/jvm-reordering/

http://www.importnew.com/all-posts

http://www.importnew.com/21781.html

http://hllvm.group.iteye.com/