jdk8 ConcurrentHashMap의 소스 분석

내가 ConcurrentHashMap의 보았을 때 오늘 인터뷰 새로운 학생들이 동시 증가 효율을 잠글 이해 세그먼트에 의하여 세그먼트를 기억하는 데 시간이 너무 오래 전, 얼굴 질문을 구성,이 클래스를 다시 jdk8뿐만 아니라 덜 일반적인 비즈니스 코드를 사용, 무시 그리고 실수, 환영 비판이있다,를 기록해, 오늘 데리러 봐

차이의 jdk7 및 jdk8

jdk7使用ReentrantLock와 세그먼트 + + + hashentry 안전
jdk8使用동기화 된 CAS + + + 노드 NodeTree + 불안전

주요 방법

가장 중요한 두 가지 방법에서 말하기, GET, PUT

나 동시의 초점 방법을 넣어 보자, 읽기는 상대적으로 간단하고 데이터에 대한 변경 사항을 포함하지 않는, 그것은 고정하지 않습니다. 데이터는 분명히 더 이해이 ConcurrentHashMap의 작동 방법을 알고 로직을 넣을 수있을 것입니다

풋 방법

무한 루프 로직, 전류 목표 값을 검사 테이블

  1. 초기의 경우 테이블, 다시 자전거를 초기화 할 경우, 테이블을 확인
  2. 설정 실패 (실패가있을 수있는 경우 모듈로 연산의 해시 값이 계산 된 배열 인덱스 인해 다른 스레드로, 값이 NULL이면,이어서 CAS 완료 성공적 경우의 인덱스로 설정 배열 인덱스의 값을 삭제 하부 목표 값을 설정) 재순환
  3. 보류
  4. 타겟 전류 값은 동기 부호 블록 널 없으면
    1. 변화가 있는지 기본 케이스의 현재 값을 확인, 현재의 재순환을 종료 변화가 목록을 변경하고 경우가되지 않은, 아주 키를 비교할지 여부를 설정할 새 값이 동일, 기본 논리가 더 나은 가치를 이해 제거, 동일하지 다시 목록을 탑재 반면 레코드 목록의 길이
    2. 레드 - 블랙 트리 경우, 값은 레드 - 블랙 트리로 설정됩니다 넣어 (레드 - 블랙 트리가 여기에 확장되지 않음)
    3. 레드 - 블랙 트리로 변환 여부를 목록의 길이는,이 결정되는 기본 임계 값은 8

그림 명확

소스 (플러스 주석의 핵심 부분)

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<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // 如果table为空, 初始化table, 详见下面
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // 判断当前hash 的位置有没有值,没有值, 直接使使cas 无阻塞设置
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                // 只是锁住单个对象, 锁粒度更小
                synchronized (f) {
                    // 再次检查是否有变更
                    if (tabAt(tab, i) == f) {
                        // 如果这个节点hash 值不为0, 意思是当前节点为普通节点的时候, 这里应该比较容易理解, 比较hash 值, key equals 是否相等, 如果hash 冲突就添加链表, 记录链表长度(binCount),之后会根据长度调整, 是否使用红黑树代替链表
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> 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<K,V> pred = e;
                                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;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                // 检查说阀值,默认是8, 超过会转换成树
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
复制代码

방법을 얻을 수 (해설 노트)

이 방법은 비교적 간단하고 많이 얻을, 주 처리 로직을 넣어왔다 방법

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // node 是红黑树时,查找对应节点
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
                
            // 为链表时, 循环找出对应节点
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
复制代码

표 기본 배열지도 초기화 (선택 사항)

우리는 두 개의 전면의 기본 개념을 이해할 필요가

  1. 위험한

클래스에 대한 간단한 이야기. 자바는 직접 기본 운영 체제에 액세스 할 수 있지만 로컬 (기본) 메소드를 통해 액세스 할 수 있습니다. 그러나 그럼에도 불구하고, JVM 또는 하드웨어 수준의 원자 작업을 제공 안전하지 않은 클래스에서 뒷문, JDK를 엽니 다.

공개하지만, 사용할 수있는 방법은 없습니다 방법이 클래스 있지만, JDK API 문서는이 클래스의 방법의 설명을 제공하지 않았다. 안전하지 않은 클래스의 사용이 제한됩니다 위해 모두 모두, 만 코드는 무료로 사용할 수 물론 JDK 클래스 라이브러리의 클래스의 신용 인스턴스를 얻을 수 있습니다.

  1. CAS

CAS는, 비교하고 그 비교 및 ​​교환, 종종 CAS의 상단에 내장 전체 패키지를 완료 java.util.concurrent의 동시 알고리즘의 설계에 사용되는 기술을 스왑, CAS 상관 없음이 패키지 수 없습니다, CAS의 중요성이 표시되지 않습니다.

현재 프로세서 기본 지원 CAS,하지만 서로 다른 제조업체의 실현은 아무것도 동일하지 않습니다. CAS는 세 개의 피연산자가 있습니다 메모리 값 V를 A와 V의 기대 값이 동일한 메모리 값 만 경우, 메모리 값이 B로 수정 true를 돌려주는 경우, 기존의 (A)의 기대 값, B 값은, 그렇지 않으면 아무것도 수정되지 수 수행 및 false를 돌려줍니다.

  1. 근원

배열 크기를 초기화하여 고정되지 않은 경우 sizeCtl 변수를 사용하므로,이 변수는 테이블 초기화 나타내고 -1로 설정된다.

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
        // sizeCtl: table 初始化和resize的标志位,表初始化和调整大小控件。当为负值时,将初始化或调整表的大小
            if ((sc = sizeCtl) < 0)
                // 如果是-1 表示正在初始化或者调整大小, 这时放弃cpu使用, 进行下一次循环检查
                Thread.yield(); // lost initialization race; just spin
            // 设置SIZECTL为-1,设置成功开始初始化, 不成功继续循环。  
            // compareAndSwapInt 非阻塞同步原语: arg0, arg1, arg2, arg3 分别为对象实例,目标对象属性,当前预期值,要设的值, 设置成功返回 true, 失败 false
            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<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }
复制代码

개요

1. 用 Synchronized + CAS + Node + NodeTree 代替 Segment ,只有在hash 冲突, 或者修改已经值的时候才去加锁, 锁的粒度更小,大幅减少阻塞
2. 链表节点数量大于8时,会将链表转化为红黑树进行存储,查询时间复杂度从O(n),变成遍历红黑树O(logN)。复制代码

추천

출처juejin.im/post/5d819331e51d4561b674c511