22.04.17--22.04.24(本周作业)

目录

1.Java内存模型(Java Memory Model)的理解? 注意不是运行时数据区(Java Runtime Area)

2. ConcurrentHashMap的put源码分析

1.Java内存模型(Java Memory Model)的理解? 注意不是运行时数据区(Java Runtime Area)

  • 类加载器(ClassLoader)
  • 运行时数据区(Runtime Data Area)
  • 执行引擎(Execution Engine)
  • 本地库接口(Native Interface)

组件的作用:首先通过类加载器会把Java代码转换成字节码,运行时数据区再把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎,将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程需要调用其他语言的本地库接口来实现整个程序的功能。

2. ConcurrentHashMap的put源码分析

大概走了一遍ConcurrentHashMap的put过程。源码是JDK 11的,跟JDK 8基本没有什么改动,但与JDK 7相差甚大。主要核心思想是以某个位置的头结点(链表的头结点或红黑树的root结点)为锁,配合自旋+CAS避免不必要的锁开销。

public V put(K key, V value) {
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // key 和 value都不能为空
    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; K fk; V fv;
        // 如果没有初始化,就先进行初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 根据hash,取table上相应位置的第一个结点f,判断它是否为空
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 如果为空,用cas添加进去
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;                   // 直接结束循环
        }
        // 如果当前节点是一个forwarding节点,表示在resize过程中
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 如果在resize过程中,帮助转换
        else if (onlyIfAbsent // 检查第一个没有申请锁的结点
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        else {
            // 接下段代码块
        }
    }
    // 元素计数加1,根据binCount来检验是否需要检查和扩容
    // 如果<0,不会检查。这里不会小于0
    // 如果<=1,会检查uncontended。
    // 如果>1,会检查并尝试扩容
    addCount(1L, binCount);
    return null;
}
else {
    V oldVal = null;
    synchronized (f) { // 以第一个结点为锁
        // 如果f是第一个结点
        if (tabAt(tab, i) == f) {
            // 如果f的hash大于0,说明不是在特殊状态,也不是红黑树
            if (fh >= 0) {
                binCount = 1;
                // 死循环,每次binCount加1
                for (Node<K,V> e = f;; ++binCount) {
                    K ek;
                    // 如果当前节点的hash等于key的hashCode,
                    // 且当前节点的key equals key
                    if (e.hash == hash &&
                        ((ek = e.key) == key ||
                         (ek != null && key.equals(ek)))) {
                        oldVal = e.val; // 说明key已经存在,获取原来的值
                        if (!onlyIfAbsent)
                            e.val = value; // 替换这个节点的值
                        break; // 退出死循环
                    }
                    Node<K,V> pred = e;
                    // 如果弄到最后还是为空,说明key不存在
                    if ((e = e.next) == null) {
                        // 在链表尾部插入一个新的节点
                        pred.next = new Node<K,V>(hash, key, value);
                        break; // 结束内层死循环
                    }
                }
            }
            // 如果是红黑树结点
            else if (f instanceof TreeBin) {
                Node<K,V> p;
                binCount = 2;
                // 调用红黑树的put方法,其内部也是使用的CAS,并判断key是否已存在
                if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                      value)) != null) {
                    oldVal = p.val; // key存在,得到原来值
                    if (!onlyIfAbsent)
                        p.val = value; // 设置新值
                }
            }
            // 如果是保留的Node类型
            else if (f instanceof ReservationNode)
                throw new IllegalStateException("Recursive update");
        }
    }
    // 如果binCount不为0,插入成功了,否则会进入再一次的循环
    if (binCount != 0) {
        // 如果大于转换红黑树的阈值
        if (binCount >= TREEIFY_THRESHOLD)
            // 转换成红黑树,内部以根结点为锁
            treeifyBin(tab, i);
        // 如果key已存在,有原值,返回原值
        if (oldVal != null)
            return oldVal;
        break; // 结束外层死循环
    }
}

 Reference:

// 再hash, 跟HashMap的再哈希类似,让高位和低位同时参与
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

/*
 * Encodings for Node hash fields. See above for explanation.
 */
static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

static final class ReservationNode<K,V> extends Node<K,V> {
    ReservationNode() {
        super(RESERVED, null, null);
    }

    Node<K,V> find(int h, Object k) {
        return null;
    }
}

没有细看扩容相关的helpTransfer、addCount,以及初始化方法initTable,以后有机会再分析。

总结一下put的过程:

  1. 先检查key和value都不能为空,如果为空,就抛出空指针异常;
  2. 对key再哈希,防止用户自己写的哈希算法导致分布不均匀;
  3. 检查是否已经进行了初始化,如果没有,要先初始化;
  4. 根据再hash的值,计算在table上的位置的第一个结点f,如果f为空,用CAS直接插入;否则,进行第5步;
  5. 检查f是否是一个forwarding结点,如果是表示正在扩容,当前线程就去帮助扩容;如果不是,进行第6步;
  6. 检查f的key是不是等于要插入的key,如果是,获取原来的值并替换成新的值,如果不是,进行第7步;
  7. 以第一个结点为锁;
  8. 如果f不是红黑树结点,进行第9步,如果是红黑树结点,进行第11步;
  9. 根据链表向下找,如果找到key相等的位置,获取原来的值并替换成新的值;
  10. 如果一直到链表末尾也没有找到相等的key,就在链表尾部插入一个新节点;
  11. 调用红黑树的putTreeVal方法,其内部是使用CAS,也会判断key是否已经存在并返回旧值;
  12. 如果是保留的Node类型ReservationNode,直接抛出异常;
  13. 根据binCount判断是否达到链表转红黑树的阈值,如果达到了,转成红黑树,这个过程会以根节点为锁;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值