JDK15源码(二):ConcurrentHashMap(插入元素、初始化、扩容、计数、树化)

本文详细剖析了Java 8中ConcurrentHashMap的实现原理,包括其并发控制机制、CAS锁与Unsafe类的应用、扩容及树化过程、线程安全的计数方法。通过对内部数据结构和操作细节的讲解,展示了ConcurrentHashMap如何在高并发场景下保证性能和线程安全。

Unsafe

ConcurrentHashMap使用Synchronized和CAS锁对线程进行并发控制。CAS锁,也就是自旋锁,即比较并交换。ConcurrentHashMap的CAS锁是使用jdk.internal.misc.Unsafe类实现的,而jdk.internal.misc.Unsafe实际是调用sun.misc.Unsafe。

sun.misc.Unsafe的无参构造函数是私有private的,所以不能直接通过new实例化的方式获得一个对象,只能通过反射的方式获取。sun.misc.Unsafe类提供了三个CAS方法:compareAndSwapInt、compareAndSwapLong和compareAndSwapObject,分别是对int、long和object类型的变量做比较交换。这三个方法都有4个入参,第一个参数表示要作用的对象,第二个参数表示要作用的变量,第三个参数表示期望值,第四个参数是修改变成的值,当变量的值与第三个参数相同时,才会将变量的值修改为第四个参数,如果修改成功会返回true,修改失败返回false。示例如下:

import sun.misc.Unsafe;
import java.lang.reflect.Field;
class Person {
   
   
    public int age;

    public int getAge() {
   
   
        return age;
    }

    public void setAge(int age) {
   
   
        this.age = age;
    }
}

@Test
public void testUnsafe() {
   
   
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    Person person = new Person();
    try {
   
   
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        //定义要作用在哪个变量上
        final long COUNT_OFFSET = unsafe.objectFieldOffset(person.getClass().getDeclaredField("age"));
        boolean b = unsafe.compareAndSwapInt(person, COUNT_OFFSET, 0, 1);
        System.out.println(b);
        System.out.println(person.getAge());
    } catch (Exception e) {
   
   
        e
        .printStackTrace();
    }
}

comparableClassFor(Object x)

假设对象x的Class类型为C,如果Class C实现了Comparable接口,Comparable有且仅有一个泛型参数,是这个类本身C,也就是:

class C implements Comparable<C>

调用ConcurrentHashMap的comparableClassFor()方法会返回C.class。

static Class<?> comparableClassFor(Object x) {
   
   
    //判断是否是Comparable的子类,如果不是直接返回null
    if (x instanceof Comparable) {
   
   
        Class<?> c; Type[] ts, as; ParameterizedType p;
        //如果参数x的Class类型是String,直接返回class java.lang.String
        if ((c = x.getClass()) == String.class) // bypass checks
            return c;
        //获取实现的父类接口类型    
        if ((ts = c.getGenericInterfaces()) != null) {
   
   
            //遍历接口类型
            for (Type t : ts) {
   
   
                //判断该接口是否实现了泛型 && 接口的类型对象是否是Comparable 
                // 泛型参数只有一个 && 泛型参数类型就是入参的Class类型
                if ((t instanceof ParameterizedType) &&
                    ((p = (ParameterizedType)t).getRawType() == Comparable.class) && (as = p.getActualTypeArguments()) != null &&
     as.length == 1 && as[0] == c)
                    return c;
            }
        }
    }
    return null;
}

Class的getGenericInterfaces()方法返回的是实现的接口的类型数组。

public Type[] getGenericInterfaces() {
   
   
    ClassRepository info = getGenericInfo();
    return (info == null) ?  getInterfaces() : info.getSuperInterfaces();
}

接口ParameterizedType继承了接口Type。ParameterizedType是带有类型参数的类型,即常说的泛型,如Collection。

Type[] getActualTypeArguments(); //返回一个Type数组,数组里是参数化类型的参数
Type getRawType();//返回声明此类型的类或接口

示例:

List<String> list = new ArrayList<String>();
Type[] genericInterfaces = list.getClass().getGenericInterfaces();
for(Type type:genericInterfaces) {
   
   
    if(type instanceof ParameterizedType) {
   
   
        ParameterizedType parameterizedType = (ParameterizedType) type;
        System.out.println(Arrays.asList(parameterizedType.getActualTypeArguments()));//[E]
        System.out.println(parameterizedType.getRawType());//interface java.util.List
    }
}

compareComparables(Class<?> kc, Object k, Object x)

compareComparables是用来比较两个对象的大小。如果x为空,或者类型不是kc,返回0。如果x不为空并且x的类型是kc,返回k.compareTo(x)的比较结果。

static int compareComparables(Class<?> kc, Object k, Object x) {
   
   
    return (x == null || x.getClass() != kc ? 0 :
            ((Comparable)k).compareTo(x));
}

ConcurrentHashMap插入元素

ConcurrentHashMap.put()方法会调用putVal()方法插入元素。putVal()方法的第三个布尔变量onlyIfAbsent如果为true,表示只有在不存在相同的元素(hashcode和key值相等)才允许插入。如果存在相同的元素,putVal()方法会返回旧值,否则,返回null。与HashMap不同的是,ConcurrentHashMap不支持null键和null值。

(1)如果数组为null或者长度为0,初始化数组。
(2)根据hash算出待插入元素存放在数组的下标,判断该下标是否存在元素,如果不存在,使用CAS锁直接插入元素。
(3)判断数组是否正在扩容,如果是,当前线程加入扩容。
(4)判断该下标位置上的元素与待插入元素的hash和key是否相等,如果相等,表示是同一个元素,直接返回。
(5)对该下标位置的元素进行Synchronized同步,遍历链表/红黑树,插入元素。链表使用尾插法。
(6)计数。

final V putVal(K key, V value, boolean onlyIfAbsent) {
   
   
    //如果key或者value为null,抛出空指针异常。
    if (key == null || value == null) throw new NullPointerException();
    //将高16位和低16位进行异或运算,目标是得到尽可能不同的值。
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
   
   
        //i表示待插入元素放在数组位置的下标,f表示tab[i]的元素,n表示tab的长度
        //fh表示tab[i]元素的hash值,fk表示tab[i]元素的key,fv表示tab[i]元素的value值
        Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            //当数组为空或者数组的长度为0,对数组进行初始化
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
   
   
            //如果f为空,表示tab[i]没有存放元素,使用cas锁,期望值是null,最后替换为待插入节点(hash, key, value)
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break; //跳出循环,向空槽中添加元素不会使用锁
        }
        else if ((fh = f.hash) == MOVED)
            //如果tab[i]的hash值是MOVED(-1),表示正在进行扩容,当前线程也会加入扩容
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值