线程安全集合类

早期线程安全集合类

1、如:Hashtable、Vector

2、所有方法都使用 synchronized,确保线程安全

3、性能差,不建议使用

 

Collections

1、使用 Collections 装饰的线程安全集合

(1)Collections.synchronizedCollection

(2)Collections.synchronizedList

(3)Collections.synchronizedMap

(4)Collections.synchronizedSet

(5)Collections.synchronizedNavigableMap

(6)Collections.synchronizedNavigableSet 

(7)Collections.synchronizedSortedMap

(8)Collections.synchronizedSortedSet

2、装饰器模式

(1)使用内部类,保存传入的不安全集合

(2)只使用 synchronized 重写方法

(3)性能没有提高

 

java.util.concurrent.* 下的线程安全集合类

1、此包下的类的单方法线程安全,但组合方法线程不安全

2、Blocking

(1)大部分实现基于锁

(2)提供阻塞方法

3、CopyOnWrite

(1)拷贝再修改,避免并发安全

(2)修改开销相对较重

(3)适用于读多写少

4、Concurrent

(1)内部很多操作使用 CAS 优化,一般可以提供较高吞吐量

(2)遍历时弱一致性,如:当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,但内容是旧

(3)求大小弱一致性:size 操作未必是 100% 准确

(4)读取弱一致性

5、如果遍历时发生修改

(1)对于 java.util.concurrent.* 安全容器:使用 fail-safe 机制,如果容器发生修改,可以继续进行遍历,不抛出异常

(2)对于非安全容器:使用 fail-fast 机制,如果容器发生修改,则遍历立刻失败,抛出 ConcurrentModificationException,不再继续遍历

6、对于 CopyOnWrite、Concurrent 都有弱一致性

(1)如:数据库 MVCC(多版本并发控制)

(2)高并发、强一致性之间矛盾,需要权衡

 

HashMap

1、JDK 7

(1)头插法

(2)扩容时,存在并发死链问题

2、JDK 8

(1)尾插法

(2)扩容时,保持与扩容前相同顺序

 

JDK 8 ConcurrentHashMap

1、重要属性、内部类

// 默认为 0
// 当初始化时, 为 -1
// 当扩容时, 为 -(1 + 扩容线程数)
// 当初始化或扩容完成后,为 下一次的扩容的阈值大小
private transient volatile int sizeCtl;

// 整个 ConcurrentHashMap 就是一个 Node[]
static class Node<K,V> implements Map.Entry<K,V> {}

// hash 表
transient volatile Node<K,V>[] table;

// 扩容时,新 hash 表
private transient volatile Node<K,V>[] nextTable;

// 扩容时从后往前遍历Node[],迁移处理Node后,添加一个ForwardingNode作为头节点
// 扩容时如果某个 bin 迁移完毕, 用 ForwardingNode 作为旧 table bin 的头结点
// 表示该下标的Node已迁移,会在新Node[]处理请求
static final class ForwardingNode<K,V> extends Node<K,V> {}

// 用在 compute 以及 computeIfAbsent 时, 用来占位, 计算完成后替换为普通 Node
static final class ReservationNode<K,V> extends Node<K,V> {}

// 作为 treebin 的头节点, 存储 root 和 first
static final class TreeBin<K,V> extends Node<K,V> {}

// 作为 treebin 的节点, 存储 parent, left, right
static final class TreeNode<K,V> extends Node<K,V> {}

2、重要方法

// 获取 Node[] 中第 i 个 Node
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i)
 
// cas 修改 Node[] 中第 i 个 Node 的值, c 为旧值, v 为新值
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v)
 
// 直接修改 Node[] 中第 i 个 Node 的值, v 为新值
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)

3、构造器

(1)懒惰初始化:只计算 table 大小,在第一次使用时才会真正创建

(2)设置的初始大小,不一定为所设定大小,必定为 2n

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    //若初始容量小于并发度
    if (initialCapacity < concurrencyLevel) // Use at least as many bins
        //确保两者相等
        initialCapacity = concurrencyLevel; // as estimated threads
    //计算初始大小
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    // tableSizeFor 保证计算的初始大小为 2^n 
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
        MAXIMUM_CAPACITY : tableSizeFor((int)size);
    //确认下一次扩容容量,即创建时
    this.sizeCtl = cap;
}

4、get

(1)无锁

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // spread 方法能确保返回结果是正数
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        //tabAt定位桶下标,即定位数组下标的头节点,且不为null
        //(n - 1) & h,相当于取模,但效率更高
        (e = tabAt(tab, (n - 1) & h)) != null) {
        //对比hash值
        if ((eh = e.hash) == h) {
            //对比是否相等
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // hash 为负数表示该 bin 在扩容中(ForwardingNode的hash小于0),或是 treebin(红黑树), 这时调用 find 方法来查找
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 正常遍历链表, 用 equals 比较
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

5、put

(1)以下数组简称(table),链表简称(bin)

public V put(K key, V value) {
    return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
    //与HashMap不同,key、value都不允许为null
    if (key == null || value == null) throw new NullPointerException();
    //获取key的hash,其中spread会综合高位低位, 具有更好hash性
    int hash = spread(key.hashCode());
    //链表最大下标(长度)
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        // f 是链表头节点
        // fh 是链表头节点的 hash
        // i 是链表在 table 中的下标
        Node<K,V> f; int n, i, fh;
        //未创建Node[],则创建 table,懒惰初始化
        if (tab == null || (n = tab.length) == 0)
            // 初始化 table 使用CAS,不需要synchronized,创建成功,进入下一轮循环
            tab = initTable();
        //该下标未有头节点,则创建链表头节点
        //tabAt(tab, i = (n - 1) & hash):查找桶下标
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 添加链表头使用CAS,无需 synchronized
            if (casTabAt(tab, i, null,
                         new Node<K,V>(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) {
                        binCount = 1;
                        // 遍历链表
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //查找相同key
                            if (
                                //对比hash
                                e.hash == hash &&
                                //是否为同一对象
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                //取出旧value
                                oldVal = e.val;
                                //onlyIfAbsent:false:会用新value覆盖旧value;true:不会用新value覆盖旧value
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            //没有相同key,已经是最后的节点,新增 Node,追加至链表尾
                            if ((e = e.next) == null) 
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                            break;
                        }
                    }
                }
                //是否为红黑树
                else if (f instanceof TreeBin) {
                    Node<K,V> p;
                    binCount = 2;
                    // putTreeVal 查看 key 是否已经在树中, 是, 则返回对应的 TreeNode
                    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                          value)) != null) {
                        oldVal = p.val;
                        if (!onlyIfAbsent)
                            p.val = value;
                    }
                }
            }
            // 释放链表头节点的锁
        }
        //检查链表是否需要扩容或树化
        //binCount != 0,说明链表有冲突
        if (binCount != 0) {
            // 如果链表长度 >= 树化阈值(8), 进行链表转为红黑树
            if (binCount >= TREEIFY_THRESHOLD)
                //尝试将链表转化为红黑树,需要链表长度 > 64,且仍有链表长度 >= 8
                treeifyBin(tab, i);
            if (oldVal != null)
                return oldVal;
            break;
        }
    }
    //类似LongAdder,设置多个累加单元
    // 增加 size 计数
    // addCount除了维护size计数,还有扩容逻辑
    addCount(1L, binCount);
    return null;
}
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    //检查hash表是否创建,其它线程会在 while 循环中 yield 直至 table 创建
    while ((tab = table) == null || tab.length == 0) {
        //sc接收初始容量值,sc < 0,表示已有线程在创建hash表,其余线程进入忙等待
        if ((sc = sizeCtl) < 0)
            Thread.yield();
        //CAS尝试将 SIZECTL 设置为 -1,表示初始化 table
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            // 获得锁, 创建 table
            try {
                //再次检查hash表是否创建
                if ((tab = table) == null || tab.length == 0) {
                    //初始容量 = 0,则使用默认值16,否则使用设定值
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    //计算下一次扩容时的阈值
                    sc = n - (n >>> 2);
                }
            } finally {
                //下一次扩容时的阈值赋值到sizeCtl
                sizeCtl = sc;
            }
            break;
        }
    }
    //返回已创建的hash表
    return tab;
}
//x:1L
//check:binCount,链表最大下标(长度)
private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    if (
        // 存在CounterCell[],则在 counterCells 上累加
        (as = counterCells) != null ||
        // 不存在CounterCell[],向 baseCount 累加
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)
    ) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (
            // 还没有CounterCell[]
            as == null || (m = as.length - 1) < 0 ||
            // CounterCell[]还没有CounterCell
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            // CAS增加CounterCell计数失败
            !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
        ) {
            // 创建CounterCell[]和CounterCell,重试累加(死循环)
            fullAddCount(x, uncontended);
            return;
        }
        //链表长度 > 1,可能需要扩容
        if (check <= 1)
            return;
        // 获取数组所有节点数
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        
        while (
            //数组元素大于扩容阈值
            s >= (long)(sc = sizeCtl) &&
            (tab = table) != null &&
            (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            //已经有线程在扩容
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                // newtable 已经创建,帮忙扩容
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 需要扩容,newtable 未创建
            //CAS将SIZECTL设为负数,表示进入扩容
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                //tab:原hash表
                //null:新hash表,懒惰初始化
                transfer(tab, null);
            s = sumCount();
        }
    }
}

6、size

(1)size 计算实际发生在 put,remove,即改变集合元素的操作之中

(2)没有竞争发生,向 baseCount 累加计数

(3)有竞争发生,新建 CounterCell[],向其中的一个 CounterCell 累加计数

(4)CounterCell[] 初始有两个 CounterCell

(5)如果计数竞争比较激烈,会创建新的 CounterCell 来累加计数

(6)多线程情况下,获取所有节点数量值,可能存在偏差

public int size() {
    //获取计数值
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}
final long sumCount() {
    //并发时的个数变动保存在 CounterCell[] 当中
    CounterCell[] as = counterCells; CounterCell a;
    //节点个数保存在 baseCount 中
    // 将 baseCount 计数与所有 CounterCell 计数累加
    long sum = baseCount;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

7、transfer

(1)以下数组简称(table),链表简称(bin)

(2)扩容时以 bin 为单位进行,需要对 bin 进行 synchronized

(3)其它竞争线程会帮助把其它 bin 进行扩容

(4)扩容时平均只有 1/6 的节点会复制到新 table 中

//tab:旧hash表
//nextTab:新hash表
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    //n:旧hash表(数组)长度
    int n = tab.length, stride;
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE; // subdivide range
    //若新hash表为null,则初始化
    if (nextTab == null) {            // initiating
        try {
            @SuppressWarnings("unchecked")
            //n << 1,即新长容量 = 旧容量 * 2
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {      // try to cope with OOME
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        nextTable = nextTab;
        transferIndex = n;
    }
    int nextn = nextTab.length;
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    boolean advance = true;
    boolean finishing = false; // to ensure sweep before committing nextTab
    //以链表为单位,迁移旧数组元素到新数组
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        while (advance) {
            int nextIndex, nextBound;
            if (--i >= bound || finishing)
                advance = false;
            else if ((nextIndex = transferIndex) <= 0) {
                i = -1;
                advance = false;
            }
            else if (U.compareAndSwapInt
                     (this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ?
                                   nextIndex - stride : 0))) {
                bound = nextBound;
                i = nextIndex - 1;
                advance = false;
            }
        }
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            if (finishing) {
                nextTable = null;
                table = nextTab;
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;
                finishing = advance = true;
                i = n; // recheck before commit
            }
        }
        //表示已移动该下标的链表头
        else if ((f = tabAt(tab, i)) == null)
            //将ForwardingNode替换链表头
            advance = casTabAt(tab, i, null, fwd);
        //表示该下标为已经为ForwardingNode,其hash == -1
        else if ((fh = f.hash) == MOVED)
            //进入下一轮循环
            advance = true; // already processed
        else {
            //锁住链表
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    Node<K,V> ln, hn;
                    //表示为链表节点
                    if (fh >= 0) {
                        int runBit = fh & n;
                        Node<K,V> lastRun = f;
                        for (Node<K,V> p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                lastRun = p;
                            }
                        }
                        if (runBit == 0) {
                            ln = lastRun;
                            hn = null;
                        }
                        else {
                            hn = lastRun;
                            ln = null;
                        }
                        for (Node<K,V> p = f; p != lastRun; p = p.next) {
                            int ph = p.hash; K pk = p.key; V pv = p.val;
                            if ((ph & n) == 0)
                                ln = new Node<K,V>(ph, pk, pv, ln);
                            else
                                hn = new Node<K,V>(ph, pk, pv, hn);
                        }
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                    //表示为红黑树节点
                    else if (f instanceof TreeBin) {
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> lo = null, loTail = null;
                        TreeNode<K,V> hi = null, hiTail = null;
                        int lc = 0, hc = 0;
                        for (Node<K,V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            TreeNode<K,V> p = new TreeNode<K,V>
                                (h, e.key, e.val, null, null);
                            if ((h & n) == 0) {
                                if ((p.prev = loTail) == null)
                                    lo = p;
                                else
                                    loTail.next = p;
                                loTail = p;
                                ++lc;
                            }
                            else {
                                if ((p.prev = hiTail) == null)
                                    hi = p;
                                else
                                    hiTail.next = p;
                                hiTail = p;
                                ++hc;
                            }
                        }
                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                        (hc != 0) ? new TreeBin<K,V>(lo) : t;
                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                        (lc != 0) ? new TreeBin<K,V>(hi) : t;
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                }
            }
        }
    }
}

 

JDK 7 ConcurrentHashMap

1、维护一个 segment 数组,每个 segment 对应一把锁,Segment 继承 ReentrantLock

2、优点:如果多个线程访问不同 segment,实际没有冲突,类似 JDK 8

3、缺点

(1)Segments 数组默认大小为 16,容量初始化指定后就不能改变,固定并发度

(2)非懒惰初始化,空间占用不友好

4、构造器

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;
    // ssize 必须是 2^n,表示 segments 数组的大小
    int sshift = 0;
    int ssize = 1;
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    // segmentShift 默认是 32 - 4 = 28
    this.segmentShift = 32 - sshift;
    // segmentMask 默认是 15,即 00000000 00001111
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;
    // 创建 segments,同时创建 segments[0],即 HashEntry[]
    Segment<K,V> s0 =
        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                         (HashEntry<K,V>[])new HashEntry[cap]);
    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
    this.segments = ss;
}

5、put

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    // 计算出 segment 下标
    // segmentShift、segmentMask 将 key 的 hash 结果匹配到 segment
    // 根据某一 hash 值求 segment 位置,先将hash 逻辑右移 this.segmentShift 位,结果再与 this.segmentMask 做位于运算
    int j = (hash >>> segmentShift) & segmentMask;

    // 获得 segment 对象, 判断是否为 null, 是则创建该 segment
    if ((s = (Segment<K,V>)UNSAFE.getObject 
         (segments, (j << SSHIFT) + SBASE)) == null) {
        // 这时不能确定是否真的为 null, 因为其它线程也发现该 segment 为 null,
        // 因此在 ensureSegment 里用 cas 方式保证该 segment 安全性
        s = ensureSegment(j);
    }
    // 进入 segment 的put 流程
    return s.put(key, hash, value, false);
}

(1)Segment 继承可重入锁(ReentrantLock)

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 尝试加锁
    HashEntry<K,V> node = tryLock() ? null :
    // 如果不成功, 进入 scanAndLockForPut 流程
    // 如果是多核 CPU,最多 tryLock 64 次,否则进入 lock 流程
    // 在尝试期间, 查看该节点在链表中是否存在, 如果没有则创建
    scanAndLockForPut(key, hash, value);

    // 执行到这里 segment 已经被成功加锁, 可以安全执行
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                // 更新
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) { 
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                // 新增
                // 1) 之前等待锁时, 可能 node 已经被创建, next 指向链表头
                if (node != null)
                    node.setNext(first);
                else
                    // 2) 创建新 node
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1; 
                // 3) 扩容
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    // 将 node 作为链表头
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

(2)扩容:rehash 发生在 put 中,因为此时已经获得了锁,因此 rehash 时不需要考虑线程安全

private void rehash(HashEntry<K,V> node) {
    //旧hash表
    HashEntry<K,V>[] oldTable = table;
    //旧容量
    int oldCapacity = oldTable.length;
    //新容量 = 旧容量 * 2
    int newCapacity = oldCapacity << 1;
    //新扩容阈值
    threshold = (int)(newCapacity * loadFactor);
    //空的新hash表
    HashEntry<K,V>[] newTable =
        (HashEntry<K,V>[]) new HashEntry[newCapacity];
    int sizeMask = newCapacity - 1;
    //遍历旧hsah表
    for (int i = 0; i < oldCapacity ; i++) {
        //e:当前遍历数组的节点
        HashEntry<K,V> e = oldTable[i];
        if (e != null) {
            //next:下一节点
            HashEntry<K,V> next = e.next;
            //e在新数组的下标
            int idx = e.hash & sizeMask;
            //链表只有一个节点,即e为头节点,有且只有一个
            if (next == null) // Single node on list
                //e直接迁移
                newTable[idx] = e;
            //链表不止一个节点
            else { // Reuse consecutive sequence at same slot
                //lastRun:当前链表节点
                HashEntry<K,V> lastRun = e;
                //lastIdx:在新数组的下标
                int lastIdx = idx;
                // 遍历链表, 尽可能重用 rehash 后 idx 不变的节点
                for (HashEntry<K,V> last = next;
                     last != null;
                     last = last.next) {
                    //k:下一节点在新数组的下标
                    int k = last.hash & sizeMask;
                    //若两节点在新数组的位置不同
                    if (k != lastIdx) {
                        //则继续遍历链表
                        lastIdx = k;
                        lastRun = last;
                    }
                }
                //若两节点在新数组的位置相同
                //则只需移动当前节点到新数组,即从链表尾部截断,在新数组相同位置的连续节点
                newTable[lastIdx] = lastRun;
                // 剩余节点需要新建
                //从头遍历链表,直到截断位置
                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                    V v = p.value;
                    int h = p.hash;
                    int k = h & sizeMask;
                    HashEntry<K,V> n = newTable[k];
                    newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                }
            }
        }
    }
    // 扩容完成, 才加入新的节点
    //计算新节点在新数组的下标
    int nodeIndex = node.hash & sizeMask; // add the new node
    //头插法
    node.setNext(newTable[nodeIndex]);
    newTable[nodeIndex] = node;

    // 替换为新的 HashEntry table
    table = newTable;
}

6、get

(1)不加锁,使用 UNSAFE 方法保证可见性,JDK 8 中同样使用 Unsafe 方法,只是封装后不可见

(2)扩容过程中,get 先发生就从旧表取内容,get 后发生就从新表取内容

(3)getObjectVolatile:不论是 Segment 或 HashEntry,都属于数组元素,只用 volatile 修饰数组不能保证可见性,需要使用 getObjectVolatile 保证数组的可见性

public V get(Object key) {
    Segment<K,V> s; // manually integrate access methods to reduce overhead
    HashEntry<K,V>[] tab;
    int h = hash(key);
    // u 为 segment 对象在数组中的偏移量,定位 segment
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    // s 即为 segment
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
             //tab.length - 1) & h:定位桶下标
             (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

7、size

(1)计算元素个数前,先不加锁计算两次,如果前后两次结果一样,认为个数正确返回

(2)如果不一样,进行重试,重试次数超过 3,将所有 segment 锁住,重新计算个数返回

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum; // sum of modCounts
    long last = 0L; // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            //RETRIES_BEFORE_LOCK == 2,retries = -1,总共有 3 次机会
            if (retries++ == RETRIES_BEFORE_LOCK) {
                // 超过重试次数, 需要创建所有 segment 并加锁
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            //遍历Segment[]
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    //seg.modCount:最近修改(put、remove)计数
                    sum += seg.modCount;
                    //seg.count:当前Segment的元素个数
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        //计数溢出int最大值
                        overflow = true;
                }
            }
            //若相等,说明统计期间没有干扰
            //若不等,重新尝试统计
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        //retries == 3,即第 3 次失败后
        if (retries > RETRIES_BEFORE_LOCK) {
            //释放segment所有锁
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

 

LinkedBlockingQueue

1、阻塞单向链表

2、节点

public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
    static class Node<E> {
        E item;

        //next三种情况
        //真正的后继节点
        //节点自身,发生在出队时
        //null, 表示没有后继节点
        Node<E> next;
        
        Node(E x) {
            item = x;
        }
    }
}

3、初始化链表

(1)last = head = new Node(null);

(2)Dummy 节点用来占位,item 为 null

4、入队

private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}

5、出队

private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    Node<E> h = head;
    //防止第二个节点不被GC
    Node<E> first = h.next;
    //next指向自己,保证原头节点被GC
    h.next = h; // help GC
    //第二个节点成为新的头节点
    head = first;
    E x = first.item;
    //第二个节点置空,即成为哨兵节点
    first.item = null;
    return x;
}

6、加锁分析

(1)一把锁,同一时刻,最多只允许有一个线程(生产者或消费者)执行

(2)两把锁,同一时刻,可以允许两个线程(一个生产者、一个消费者)同时执行

(3)消费者与消费者线程仍然串行

(4)生产者与生产者线程仍然串行

7、线程安全分析

// 用于 put(阻塞) offer(非阻塞)
private final ReentrantLock putLock = new ReentrantLock();
// 用户 take(阻塞) poll(非阻塞)
private final ReentrantLock takeLock = new ReentrantLock();

(1)当节点总数大于 2 时(包括 Dummy 节点),putLock 保证 last 节点的线程安全,takeLock 保证 head 节点的线程安全,两把锁保证入队、出队没有竞争

(2)当节点总数等于 2 时(即一个 Dummy 节点,一个正常节点),此时仍是两把锁,锁两个对象,不会竞争

(3)当节点总数等于 1 时(只有一个 Dummy 节点),这时 take 线程会被 notEmpty 条件阻塞,有竞争,会阻塞

8、put

public void put(E e) throws InterruptedException {
    //不允许元素为null
    if (e == null) throw new NullPointerException();
    int c = -1;
    //使用Node包装e
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    // count 用来维护元素计数
    final AtomicInteger count = this.count;
    //可打断
    putLock.lockInterruptibly();
    try {
        // 满则等待
        while (count.get() == capacity) {
            // 等待 notFull
            notFull.await();
        }
        // 有空位, 入队且计数加一
        enqueue(node);
        c = count.getAndIncrement(); 
        // 除了自己 put 以外, 队列还有空位, 由自己叫醒其他 put 线程
        if (c + 1 < capacity)
            //使用signal,而不是signalAll,减少竞争
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // 如果队列中有一个元素, 唤醒 take 线程
    if (c == 0)
        // 调用 notEmpty.signal(),而不是 notEmpty.signalAll(),为了减少竞争
        signalNotEmpty();
}

9、take

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    //可打断
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 如果队列中只有一个空位时, 叫醒 put 线程
    // 如果有多个线程进行出队, 第一个线程满足 c == capacity, 但后续线程 c < capacity
    if (c == capacity)
        // 这里调用的是 notFull.signal() 而不是 notFull.signalAll() 是为了减少竞争
        signalNotFull()
        return x; 
}

10、LinkedBlockingQueue、ArrayBlockingQueue 性能比较

(1)LinkedBlockingQueue 支持有界;ArrayBlockingQueue  强制有界

(2)LinkedBlockingQueue 实现是链表;ArrayBlockingQueue  实现是数组

(3)LinkedBlockingQueue 是懒惰初始化;ArrayBlockingQueue 需要提前初始化 Node 数组,但数组可重用内存,减少 GC

(4)LinkedBlockingQueue 每次入队会生成新 Node;ArrayBlockingQueue 提前创建 Node

(5)LinkedBlockingQueue 两把锁;ArrayBlockingQueue 一把锁

 

ConcurrentLinkedQueue

1、与 LinkedBlockingQueue 类似

(1)两把锁,同一时刻,可以允许两个线程(一个生产者、一个消费者)同时执行

(2)Dummy 节点,让两把锁,将来锁住的是不同对象,避免竞争

(3)区别:使用 CAS 实现锁

2、应用:如:Tomcat 的 Connector 结构时,Acceptor 作为生产者向 Poller 消费者传递事件信息时,采用 ConcurrentLinkedQueue 将 SocketChannel 给 Poller 使用

 

CopyOnWriteArrayList

1、CopyOnWriteArraySet

(1)内部维护 CopyOnWriteArrayList

private final CopyOnWriteArrayList<E> al;

(2)都通过 al 调用方法

(3)区别:Set 的元素唯一

2、读写分离

(1)底层实现:写入时拷贝

(2)增删改操作会将底层数组拷贝一份,更改操作在新数组上执行,不影响其它线程的并发读

3、应用场景:读多写少

4、相当于线程安全的 ArrayList,都为可变数组

5、与 ArrayList 不同的特性

(1)它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突

(2)线程安全

(3)因为通常需要复制整个基础数组,所以可变操作的开销很大

(4)迭代器支持 hasNext()、next() 等不可变操作,但不支持可变 remove() 等操作

(5)使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照

6、动态数组

(1)内部有 volatile 数组维护数据

(2)在添加 / 修改 / 删除数据时,都会新建一个数组,并将更新后的数据,拷贝到新建的数组中,最后再将该数组赋值给 volatile 数组

7、线程安全

(1)通过 volatile、互斥锁实现

(2)通过 volatile 数组保存数据,一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入

(3)通过互斥锁来保护数据,在添加 / 修改 / 删除数据时,先获取互斥锁,修改完毕后,先将数据更新到 volatile 数组中,然后再释放互斥锁

posted @   半条咸鱼  阅读(96)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示