0
点赞
收藏
分享

微信扫一扫

ConcurrentHashMap是如何实现线程安全的

黎轩的闲暇时光 2022-01-17 阅读 79
java

目录

前言

我们知道,在日常开发中使用的 HashMap 是线程不安全的,而线程安全类 HashTableSynchronizedMap 只是简单的在方法上加锁实现了线程安全,效率低下,所以在线程安全的环境下我们通常会使用 ConcurrentHashMap,那么 ConcurrentHashMap 又是如何实现线程安全的呢?

ConcurrentHashMap 是如何实现线程安全的

针对这个问题,可以从以下几个方面来阅读源码予以解答

初始化数据结构时的线程安全

JDK 1.8 中,初始化 ConcurrentHashMap 的时候这个 Node[] 数组是还未初始化的,会等到第一次 put() 方法调用时才初始化

final V putVal(K key, V value, boolean onlyIfAbsent) {
	if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
		Node<K,V> f; int n, i, fh;
		// 判断Node数组为空
		if (tab == null || (n = tab.length) == 0)
			// 初始化Node数组
            tab = initTable();
        ......
}

此时会有并发问题的,如果多个线程同时调用 initTable() 初始化 Node[] 数组怎么办?看看 Doug Lea 大师是如何处理的

private final Node<K,V>[] initTable() {
	Node<K,V>[] tab; int sc;
	// 每次循环都获取最新的Node[]数组引用
    while ((tab = table) == null || tab.length == 0) {
    	// sizeCtl是一个标记位,若为-1,代表有线程在进行初始化工作了
		if ((sc = sizeCtl) < 0)
			// 让出CPU时间片
			Thread.yield(); // lost initialization race; just spin
		// CAS操作,将本实例的sizeCtl变量设置为-1	
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
        	try {
            	if ((tab = table) == null || tab.length == 0) {
                	int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
            	sizeCtl = sc;
			}
            break;
		}
	}
	return tab;
}

ConcurrentHashMap 源码中 sizeCtl 属性注释如下

// 表初始化和调整控件大小。如果为负值,则表正在初始化或调整大小:
// -1用于初始化,否则-(1+活动调整大小线程的数量)
// 否则,当table为null时,将保留创建时使用的初始表大小,默认值为0。初始化后,保存下一个要调整表大小的元素计数值
private transient volatile int sizeCtl;
举报

相关推荐

0 条评论