ConcurrentHashMap在JDK1.7以及1.8的不同

ConcurrentHashMap在JDK1.7以及1.8的不同

1.结构

1.7

​ ConcurrentHashMap在JDK1.7的实现是使用 Segment数组+ HashEntry数组 组成。Segment继承了ReentrantLock,自带锁的功能。一个segment里面包含一个HashEntry数组,想要对HashEntry操作,则必须先获得Segment的锁。

ConcurrentHashMap在JDK1.7以及1.8的不同_第1张图片

1.8

​ ConcurrentHashMap JDK1.8,抛弃了Segment的设计,使用了 Node + CAS +Synchronized 的方式

ConcurrentHashMap在JDK1.7以及1.8的不同_第2张图片


2.初始化

1.7

JDK1.7的ConcurrentHashMap初始化的时候已经将Segment数组初始化,并且添加第一个Segment。c初始大小为16,代码如下

/**
     * Creates a new, empty map with the specified initial
     * capacity, load factor and concurrency level.
     *
     * @param initialCapacity the initial capacity. The implementation
     * performs internal sizing to accommodate this many elements.
     * @param loadFactor  the load factor threshold, used to control resizing.
     * Resizing may be performed when the average number of elements per
     * bin exceeds this threshold.
     * @param concurrencyLevel the estimated number of concurrently
     * updating threads. The implementation performs internal sizing
     * to try to accommodate this many threads.
     * @throws IllegalArgumentException if the initial capacity is
     * negative or the load factor or concurrencyLevel are
     * nonpositive.
     * 
     * jdk1.7
     */
    @SuppressWarnings("unchecked")
    public ConcurrentHashMap7(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        // 偏移量
        int sshift = 0;
        // Segment 数组大小
        int ssize = 1;
        // 一般应该会左移4次,sshift = 4 ssize = 16
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        // 一般是28
        this.segmentShift = 32 - sshift;
        // 掩码是ssize-1  最大值是65535 2的16次方-1
        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;
        // create segments and segments[0]
        // 第一个Segment
        Segment s0 =
                new Segment(loadFactor, (int)(cap * loadFactor),
                        (HashEntry[])new HashEntry[cap]);
        // Segment数组
        Segment[] ss = (Segment[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

1.8

JDK1.8的ConcurrentHashMap只有在第一次put的时候才开始初始化 Node 数组(inittable()),构造方法的时候只记录大小值,代码如下

/*
 * jdk 1.8 concurrent
 */
public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

/*
 * 初始化table
 */
private final Node[] initTable() {
        Node[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            // sizeCtl map大小值  
            if ((sc = sizeCtl) < 0)
                // lost initialization race; just spin
                // 线程让步 不释放锁
                Thread.yield(); 
            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[] nt = (Node[])new Node[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

3. put

1.7

JDK1.7添加元素,先通过key的Hash值,根据掩码散列定位对应的Segment,如果没有找到初始化的segment,就通过CAS生成,再通过segment的put方法插入元素。

segment插入过程,会先通过tryLock获取锁,然后将对应元素插入到HashEntry数组中,然后再释放锁。如果一开始tryLock没有获取锁,会调用scanAndLockForPut()方法获取锁(while(tryLock()),或有重试次数限制,超过的话会将线程挂起,等待获取锁的线程处理结束。

代码如下:

/**
 * concurrentHashMap的put方法
 */
public V put(K key, V value) {
    Segment s;
    if (value == null)
        throw new NullPointerException();
    // 定位Segment
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        // 通过CAS初始化Segment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

/*
 * segment 的put
 */
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 获取锁 成功则继续,失败走scanAndLockForPut 重复tryLock
    HashEntry node = tryLock() ? null :
            scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry first = entryAt(tab, index);
        for (HashEntry 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 {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

1.8

JDK1.8的添加元素方法,也是先通过key的hash值散列定位对应的Node。判断流程如下:

  1. 是否存在Node数组,没有initTable();
  2. 是否找得到对应的Node节点,没有的话CAS插入元素
  3. Node的hash值是否处于移动状态(-1)
  4. 节点加锁(synchronized),判断Node是什么结构(链表or Tree),走相关的插入方法
  5. 判断bincount是否不为0 ,不为0以为这进行了数据操作,先判断是否需要转换成树,再判断oldVal是否有值,如果有,说明只是更新,不增加节点,直接返回旧值oldVal
  6. 统计总数添加
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[] tab = table;;) {
        Node f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // Node节点不存在,CAS 插入元素
            if (casTabAt(tab, i, null,
                         new Node(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //  处于移动状态
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        // Node节点存在,且不处于移动状态
        else {
            V oldVal = null;
            // 锁住节点
            synchronized (f) {
                // 如果 Node可以定位到
                if (tabAt(tab, i) == f) {
                    // 链表结构
                    if (fh >= 0) {
                        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;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    // 树状结构
                    else if (f instanceof TreeBin) {
                        Node p;
                        binCount = 2;
                        if ((p = ((TreeBin)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            // 节点可以被定位到
            if (binCount != 0) {
                // 是否需要转换成树结构
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 统计
    addCount(1L, binCount);
    return null;
}

4. size

1.7

JDK1.7的concurrentHashMap使用一个变量modCount进行记录是否进行了变化,如果进行了put,remove,clean的操作,则给modcount加1,统计前后比较是否modcount发生变化。

先采用不加锁的方式,连续计算元素的个数,最多计算3次:
1、如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;
2、如果前后两次计算结果都不同,则给每个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[] 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 (;;) {
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

1.8

JDK1.8通过volatile 修饰的long变量baseCount来统计元素个数,部分元素的变化个数放在counterCell数组中。通过累加baseCountCounterCell数组中的数量,即可得到元素的总个数

添加删除节点的时候通过addCount()方法来修改值。逻辑如下:

  1. 初始化时counterCells为空,在并发量很高时,如果存在两个线程同时执行CAS修改baseCount值,则失败的线程会继续执行方法体中的逻辑(put/remove方v法是一个无限for循环),使用CounterCell记录元素个数的变化;
  2. 如果CounterCell数组counterCells为空,调用fullAddCount()方法进行初始化,并插入对应的记录数,通过CAS设置cellsBusy字段,只有设置成功的线程才能初始化CounterCell数组
  3. 如果通过CAS设置cellsBusy字段失败的话,则继续尝试通过CAS修改baseCount字段,如果修改baseCount字段成功的话,就退出循环,否则继续循环插入CounterCell对象

代码如下:

public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}

/*
 * x > 0 添加数据,x < 0删除数据 ,check添加的时候是bincount,删除的时候是-1
 */
private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    // counterCells为空或者CAS失败,回到原有方法重新来
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        
        boolean uncontended = true;
        // 判断是否需要初始化CounterCell[] 数组
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    if (check >= 0) {
        Node[] 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;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

/*
 * 初始化 CounterCell 数组
 */
private final void fullAddCount(long x, boolean wasUncontended) {
    int h;
    if ((h = ThreadLocalRandom.getProbe()) == 0) {
        // force initialization 强制初始化
        ThreadLocalRandom.localInit();      
        h = ThreadLocalRandom.getProbe();
        wasUncontended = true;
    }
    // True if last slot nonempty 数组没有位置,则为true
    boolean collide = false;                
    for (;;) {
        CounterCell[] as; CounterCell a; int n; long v;
        if ((as = counterCells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {            // Try to attach new Cell
                    CounterCell r = new CounterCell(x); // Optimistic create
                    // 修改cellBusy成功的线程初始化 数组
                    if (cellsBusy == 0 &&
                        U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                        boolean created = false;
                        try {               // Recheck under lock
                            CounterCell[] rs; int m, j;
                            if ((rs = counterCells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                break;
            else if (counterCells != as || n >= NCPU)
                collide = false;            // At max size or stale
            else if (!collide)
                collide = true;
            else if (cellsBusy == 0 &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                try {
                    if (counterCells == as) {// Expand table unless stale
                        CounterCell[] rs = new CounterCell[n << 1];
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        counterCells = rs;
                    }
                } finally {
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            h = ThreadLocalRandom.advanceProbe(h);
        }
        else if (cellsBusy == 0 && counterCells == as &&
                 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
            boolean init = false;
            try {                           // Initialize table
                if (counterCells == as) {
                    CounterCell[] rs = new CounterCell[2];
                    rs[h & 1] = new CounterCell(x);
                    counterCells = rs;
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            if (init)
                break;
        }
        else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
            break;                          // Fall back on using base
    }
}

5.参考内容

《并发编程的艺术》-方腾飞 魏鹏 程晓明

本文由博客一文多发平台 OpenWrite 发布!

你可能感兴趣的:(java-ee)