数据结构 - ConcurrentHashMap 一步步深入(二)

简介
前面一篇我们介绍了ConcurrentHashMap一些重要的内部类Node 、TreeNode、TreeBin、ForwardingNode,以及ConcurrentMap接口和ConcurrentHashMap的构造函数,本篇主要介绍ConcurrentHashMap的主干方法。

ConcurrentHashMap 的hash算法

static final int spread(int h) {
    // 混合高低位,并控制范围
    return (h ^ (h >>> 16)) & HASH_BITS;
}

在HashMap中hash算法是return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 而spread方法其实就是hash算法,最后和int最大值取位与只是为了控制结果在int范围内。

ConcurrentHashMap 数组长度计算

private static final int tableSizeFor(int c) {
    int n = c - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

此方法请看HashMap篇,里面有详细推导

ConcurrentHashMap 操作数组头节点

static final  Node tabAt(Node[] tab, int i) {
    // ((long)i << ASHIFT) + ABASE 元素位置
    return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
static final  boolean casTabAt(Node[] tab, int i,
                                    Node c, Node v) {
    // 修改槽中元素从c改成v
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final  void setTabAt(Node[] tab, int i, Node v) {
    // v放到对应槽中
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

ConcurrentHashMap 添加

public V put(K key, V value) {
    // 添加
    return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 键或值为空抛异常
    if (key == null || value == null) throw new NullPointerException();
    // 获取hash
    int hash = spread(key.hashCode());
    int binCount = 0;
    // 遍历table
    for (Node[] tab = table;;) {
        Node f; int n, i, fh;
        // tab为空(第一次添加)
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
            // 查找hash所在的槽是否为空
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 创建新节点并放入对应槽中(失败重新来过)
            if (casTabAt(tab, i, null,
                    new Node(hash, key, value, null)))
                break;
        }
        // 正在扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            // 原值
            V oldVal = null;
            // 锁定头节点
            synchronized (f) {
                // 头节点没有改变过
                if (tabAt(tab, i) == f) {
                    // 头节点hash 大于 0
                    if (fh >= 0) {
                        // 链表添加初始为1
                        binCount = 1;
                        // 从头节点开始遍历(每次binCount加1)
                        for (Node e = f;; ++binCount) {
                            K ek;
                            // 键是否一样
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                // 获取原值
                                oldVal = e.val;
                                // 不覆盖开关是否打开,false就覆盖
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            // 键不一样e下移
                            Node pred = e;
                            // 后面节点为空
                            if ((e = e.next) == null) {
                                // 追加元素
                                pred.next = new Node(hash, key,
                                        value, null);
                                break;
                            }
                        }
                    }
                    // hash 小于 0 说明是TreeBin(hash为-2)
                    else if (f instanceof TreeBin) {
                        Node p;
                        // 树添加设置为2
                        binCount = 2;
                        // 调用树结构添加
                        if ((p = ((TreeBin)f).putTreeVal(hash, key,
                                value)) != null) {
                            // 原值
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                // 覆盖原值
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // binCount 大于8,链表转树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                // 原值不为空,要么覆盖要么忽略
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 能到这儿一定添加成功(需不需要扩容)
    addCount(1L, binCount);
    return null;
}

如果只看putVal方法ConcurrentHashMap 跟HashMap差不多,就把头元素锁了,但是如果你真这么认为,只能说明没有看第三篇。

ConcurrentHashMap 初始化数组

private final Node[] initTable() {
    Node[] tab; int sc;
    // table 不为空退出循环
    while ((tab = table) == null || tab.length == 0) {
        // 判断扩容阈值是否小于0
        if ((sc = sizeCtl) < 0)
            // 让出cpu
            Thread.yield();
            // 修改扩容阈值为-1
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                // 判处table是否还没被初始化
                if ((tab = table) == null || tab.length == 0) {
                    // sc > 0 使用sc,否则使用默认初始长度
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    // 创建数组
                    Node[] nt = (Node[])new Node[n];
                    // 赋值
                    table = tab = nt;
                    // 计算下次扩容阈值
                    // n - n/4 等价于 n*0.75
                    sc = n - (n >>> 2);
                }
            } finally {
                // 设置新的扩容阈值
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

ConcurrentHashMap 链表转树

// binCount(链表长度)大于8会调用这个方法
private final void treeifyBin(Node[] tab, int index) {
    Node b; int n, sc;
    // 数组不能为空
    if (tab != null) {
        // 判断是在是否小于64
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            // 扩容第三篇会讲
            tryPresize(n << 1);
        // 大于64,获取头节点,判断是否已经是TreeBin
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            // 锁住头节点
            synchronized (b) {
                // 再取一次头节点判断跟b是否一样
                if (tabAt(tab, index) == b) {
                    TreeNode hd = null, tl = null;
                    for (Node e = b; e != null; e = e.next) {
                        // 构建树节点
                        TreeNode p =
                                new TreeNode(e.hash, e.key, e.val,
                                        null, null);
                        // 构建树节点的链表结构,
                        // 初始化TreeBin时才转树结构
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    // 构建TreeBin,并把它放入当前槽
                    setTabAt(tab, index, new TreeBin(hd));
                }
            }
        }
    }
}

ConcurrentHashMap 树转链表

static  Node untreeify(Node b) {
    Node hd = null, tl = null;
    // 遍历节点
    for (Node q = b; q != null; q = q.next) {
        // 构建普通节点
        Node p = new Node(q.hash, q.key, q.val, null);
        if (tl == null)
            hd = p;
        else
            tl.next = p;
        tl = p;
    }
    return hd;
}

ConcurrentHashMap 键取值

public V get(Object key) {
    Node[] tab; Node e, p; int n, eh; K ek;
    // 计算hash
    int h = spread(key.hashCode());
    // 判断数组是否为空,然后判断槽中头节点是否为空
    // (n - 1) & h 就是取余,请看HashMap篇推导过程
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
        // 头节点hash是否一样
        if ((eh = e.hash) == h) {
            // hash一样看key是否一样
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                // key一样返回节点中的值
                return e.val;
        }
        // hash不一样,判断hash是否小于0
        // TreeBin 的hash为-2
        else if (eh < 0)
            // 在红黑树中查找
            return (p = e.find(h, key)) != null ? p.val : null;
        // 能到这说明一定是链表,通过遍历链表查找
        while ((e = e.next) != null) {
            // 判断key是否一样,并节点下移
            if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

ConcurrentHashMap 删除

public V remove(Object key) {
    return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
    // 计算hash
    int hash = spread(key.hashCode());
    // 自旋
    for (Node[] tab = table;;) {
        Node f; int n, i, fh;
        // 判断数组或头节点为空
        if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
            // 直接退出
            break;
        // 判断hash是否为移动状态
        else if ((fh = f.hash) == MOVED)
            // 如果是就帮忙移动,移动完自旋删除
            tab = helpTransfer(tab, f);
        else {
            // 能到这儿,说明不是再移动
            V oldVal = null;
            boolean validated = false;
            // 锁住头节点
            synchronized (f) {
                //  判断头节点是否变动
                if (tabAt(tab, i) == f) {
                    // 链表删除
                    if (fh >= 0) {
                        validated = true;
                        // 遍历链表
                        for (Node e = f, pred = null;;) {
                            K ek;
                            // 判断key是否一样
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                V ev = e.val;
                                // 判断值是否一样
                                if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                    // 原值
                                    oldVal = ev;
                                    // value不为空,使用value覆盖原值,
                                    // 否则移除节点,
                                    // 如果移除的是头节点,需要把设置新头节点
                                    if (value != null)
                                        e.val = value;
                                    else if (pred != null)
                                        pred.next = e.next;
                                    else
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            // 节点下移
                            pred = e;
                            if ((e = e.next) == null)
                                break;
                        }
                    }
                    // 树结构中删除
                    else if (f instanceof TreeBin) {
                        validated = true;
                        TreeBin t = (TreeBin)f;
                        TreeNode r, p;
                        // 在树结构中查找
                        if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                            V pv = p.val;
                            // 判断值是否一样
                            if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                oldVal = pv;
                                // value不为空,使用value覆盖原值,
                                // 否则,在树结构中删除
                                // 如果移除的是头节点,需要设置新头节点
                                if (value != null)
                                    p.val = value;
                                else if (t.removeTreeNode(p))
                                    setTabAt(tab, i, untreeify(t.first));
                            }
                        }
                    }
                }
            }
            // 是否查找过
            if (validated) {
                // 原值是否为空
                if (oldVal != null) {
                    // value是否为空,不为空只覆盖不删除
                    if (value == null)
                        // 长度减一
                        addCount(-1L, -1);
                    // 返回原值
                    return oldVal;
                }
                break;
            }
        }
    }
    return null;
}

ConcurrentHashMap 清空

public void clear() {
    long delta = 0L; 
    int i = 0;
    Node[] tab = table;
    // 遍历数组所有元素
    while (tab != null && i < tab.length) {
        int fh;
        // 获取头节点
        Node f = tabAt(tab, i);
        // 头节点为空,数组下标加1
        if (f == null)
            ++i;
        // 判断头节点状态
        else if ((fh = f.hash) == MOVED) {
            // 帮忙移动
            tab = helpTransfer(tab, f);
            // 移动完成,重置数组下标
            i = 0; 
        }
        // 正常删除
        else {
            // 锁住头节点
            synchronized (f) {
                // 判断头节点是否变化
                if (tabAt(tab, i) == f) {
                    // 获取链表或树的头节点
                    Node p = (fh >= 0 ? f :
                            (f instanceof TreeBin) ?
                                    ((TreeBin)f).first : null);
                    // 遍历个数
                    while (p != null) {
                        --delta;
                        p = p.next;
                    }
                    // 清空当前槽
                    setTabAt(tab, i++, null);
                }
            }
        }
    }
    // 修改总长度
    if (delta != 0L)
        addCount(delta, -1);
}

ConcurrentHashMap 在修改链表或者树时都会锁住头元素,其他修改操作就拿不到锁,他们就会等待。


你可能感兴趣的:(数据结构 - ConcurrentHashMap 一步步深入(二))