上文说了HashMap,其实HashMap是线程非安全的,JDK里面有个线程安全的就是HashTable,查看HashTable每个方法都增加了synchronized同步锁,也就说每次只能进入一个线程,这样影响效率。JDK源码也推荐使用ConcurrentHashMap。
If a thread-safe implementation is not needed, it is recommended to use HashMap in place of code Hashtable. If a thread-safe highly-concurrent implementation is desired, then it is recommended to use ConcurrentHashMap in place of code Hashtable.
如果你不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。HashTable已经被淘汰了,不要在新的代码中再使用它。
和 HashMap 非常类似,唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。原理上来说:根据 key 计算出 hashcode ,判断是否需要进行初始化,f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,多个线程操作只有一个成功,失败则自旋保证成功。如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。如果都不满足,则利用 synchronized 锁写入数据。,如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。。Mysql里面有表锁和行锁的概念。表锁就是HashTable,行锁就是ConcurrentHashMap 。
1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存在 HashMap 在 1.7 版本中的问题。那么是什么问题呢?很明显那就是查询遍历链表效率太低。
因此 1.8 做了一些数据结构上的调整。,在 JAVA8 中它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如 TreeBin、Traverser等对象内部类。如何让多线程之间,对象的状态对于各线程的“可视性”是顺序一致的:ConcurrentHashMap 使用了 happensbefore 规则来实现。happens-before规则(摘取自 JAVA 并发编程):
程序次序法则
线程中的每个动作A都 happens-before 于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
监视器锁法则
对一个监视器锁的解锁 happens-before 于每一个后续对同一监视器锁的加锁。
volatile 变量法则
对 volatile 域的写入操作 happens-before 于每一个后续对同一个域的读写操作。
线程启动法则
在一个线程里,对 Thread.start 的调用会 happens-before 于每个启动线程的动作。
线程终结法则
线程中的任何动作都 happens-before 于其他线程检测到这个线程已经终结、或者从 Thread.join 调用中成功返回,或 Thread.isAlive 返回 false。
中断法则
一个线程调用另一个线程的 interrupt happens-before 于被中断的线程发现中断。
终结法则
一个对象的构造函数的结束 happens-before 于这个对象 finalizer 的开始。
传递性
如果 A happens-before 于 B,且 B happens-before 于 C,则 A happens-before于C: 假设代码有两条语句,代码顺序是语句1先于语句2执行;那么只要语句之间不存在依赖关系,那么打乱它们的顺序对最终的结果没有影响的话,那么真正交给CPU去执行时,他们的执行顺序可以是先执行语句2然后语句1。
PS:不管是1.7的hashMap还是ConcurrentHashMap,源码的可读性变差。目前基本都是jdk1.8就没有说1.7的事情,毕竟事务都是在进化的。里面用到了很多数据结构,数据结构说难也不难,说容易也不容易,它本身就是人的思维的一种体现。好像说走路方式一样,怎么走都可以到指定的地方,但是方法不一样,数据结构就是通过更加科学方式来进行,归根到底还是看我的【数据结构与算法】专题。数据结构给你,通过算法来进行查找,如果是遍历的方式来查,可能相对于hash的形式要差一些。