在多线程并发中,HashMap会造成线程不安全的问题,ConcurrentHashMap可以用于解决此类问题,接下来记录一下HashMap和ConcurrentHashMap的不同,加深一下印象
在jdk1.8之前,HashMap是由数据加链表的形式实现的,从整体来看,HashMap是一个数组,但是每个数据元素又是一张链表,如下图:
当向HashMap中增加元素时,会先根据新增的元素Key的hash值计算出该元素将要保存在数组中的下标,如果多个元素计算出的下标值相同,就会以链表的形式存储在数组的同一个元素中。
Jdk8之前的ConcurrentHashMap间接的实现了Map<K,V>,并将每一个元素称为一个segment,每个segment都是一个HashEntry<K,V>数组(称为table),table的每个元素都是一个HashEntry的单向队列,如下图:
默认情况下,ConcurrentHashMap会生成16个segment,采用这种结构的ConcurrentHashMap解决并发问题的思想是更加细粒度的结合给Map加锁,HashTable通过给整个容器加锁的方式保障线程安全,但是如果有多个线程同时修改HashTable仍然会发生写冲突并出现并发异常,而ConcurrentHashMap不会给整个容器加锁,而是给每个segment都加一把锁,即将一把大锁拆分成多把小锁来保证线程安全,这样在第一个线程修改segment-1的同时,其余线程也可以修改其余的segment,只要各个线程同一时刻访问的是不同的segment,就不会发生写冲突。
在jdk8中,HashMap和ConcurrentHashMap存储结构增加了红黑树,当链表中的元素超过8个的时候,HashMap就会将链表转换为红黑树,即数组加链表加红黑树的存储结构,如下图
同时,ConcurrentHashMap也改为了数据加链表加红黑树的存储结构,并且废弃了segment的加锁操作,采用了volatile HashEntry<K,V>对象保存数据,即对每一条数据直接通过volatile避免冲突,同时ConcurrentHashMap还使用了大量的synchronized和CAS算法来保证线程安全。
/**
* 并发容器 - ConcurrentMap
*/
package concurrent.t06;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;
public class Test_01_ConcurrentMap2 {
public static void main(String[] args) {
// final Map<String, String> map = new Hashtable<>();
final Map<String, String> billParamMap = new ConcurrentHashMap<>();
// final Map<String, String> map = new ConcurrentSkipListMap<>();
final Random r = new Random();
Thread[] array = new Thread[100];
long begin = System.currentTimeMillis();
for(int i = 0; i < array.length; i++){
array[i] = new Thread(new Runnable() {
@Override
public void run() {
billParamMap.put("hospitalId", "1");
billParamMap.put("startTime", "20200926");
billParamMap.put("endTime", "20200927");
billParamMap.put("checkDate", "20200907");
}
});
}
for(Thread t : array){
t.start();
}
long end = System.currentTimeMillis();
System.out.println("执行时间为 : " + (end-begin) + "毫秒!");
System.out.println(billParamMap.get("hospitalId"));
}
}
结果为