HashMap, HashTable, ConcurrentHashMap都是使用同一种数据结构(数组+链表);
- HashMap多线程状态下是不安全的;
- HashTable和ConcurrentHashMap是在HashMap的基本的数据结构上进行优化,使他们在多线程下是安全的;
- HashTable是在每一个关键方法都是用synchronized加锁来确保多线程安全,例如插入,读取,对size进行操作都是加上synchronized关键字;
- ConcurrentHashMap是进一步的优化,只是写操作时对该“桶”加锁,两个线程读取不同的桶是不互斥的,每个桶都有一个锁,并且读取操作不加锁;
HashTable
- HashMap的key可以为null,HashTable的key不可以为null
- 对size的操作也是使用synchronized,效率比较低
- HashTable就是在HashMap的方法的基础上,加上一个synchronized,同步方法,即使多个线程同时调用同一个方法,也只有一个线程的方法能够执行,其他线程挂起等待,保证了线程安全;
- 还有一个就是扩容,一旦扩容,就由该线程完成一整个扩过程,所有的数据每一次拷贝都要上锁,效率非常低下,虽然保证了线程安全;
ConcurrentHash
- ConcurrentHashMap的key不能为null,HashMap的key可以为null;
- 充分使用CAS机制对size进行操作,提高了效率,避免了重量级锁的出现;
- ConcurrentHashMap就是在HashTable上的进一步优化,保证线程安全的同时提高效率;
- ConcurrentHashMap的读操作不上锁,即使多个线程同时读取数据也能同时进行;
- 但是ConcurrentHashMap对写操作上锁,我们都知道HashMap是数组+链表的数据结构,ConcurrentHashMap也是如此的数据结构,虽然ConcurrentHashMap的方法也上锁,但是每一个链表都是使用不同的锁对象,只是把锁加载链表头,所以不同线程能够同时对不同链表(不同桶)写入,但是对于同一个链表(同一个桶)就必须同一个时刻只能有一个线程进行写入操作;
对于扩容进行优化,ConcurrentHashMap使用:化整为零
- 第一个发现需要扩容的线程,只需要创建数组,然后只需要复制几个数据过去新数组;
- 后续每一个操作该数组的线程都要参加将数据从旧数组搬到新数组,一次也只是移动几个数组;
- 对于插入数据操作,查找过程新老数组都要查找,但是只插入到新数据;
- 直到旧数组的数据全部移动到新数据,就把旧数组删掉;
- 通过一点点的移动数据,以此来提高扩容效率,同时不对其他线程的操作造成影响;
拓展
ConcurrentHashMap还对数据进行了优化,由原来的数组+链表变成数组+链表/红黑树。但链表的数据个数超过8个,就会把链表变成红黑树,进一步提高效率;