자바 집합 - ConcurrentHashMap 소스 코드 개요 (JDK 1.8 기반)

21623 단어 자바 집합
목차
개술
2. 소스 코드 설명
1, 구조 함수
1.1 인삼 없 는 구조 함수
1.2 지 정 된 용량, 로드 인자, 병렬 단계 의 구조 함수
2, put 방법
2.1 initTable () Node 배열 을 초기 화 하 는 방법
2.2 addCount(long x, int check) node 의 데 이 터 를 통계 하여 확장 이 필요 하 다 면 확장 합 니 다.
3, get 방법
4, size 방법
5, remove 방법
대비
1. 병발 단계
2, 데이터 구조
3. 링크 의 삽입
4, 확장
개술
jdk 1.8 의 Concurrent HashMap 은 세그먼트 잠 금 개념 을 취소 했다. 구조 함수 에서 병발 등급 이라는 매개 변 수 를 지정 할 수 있 지만 실제 적 으로 초기 용량 이 병발 등급 보다 작 으 면 초기 용량 의 값 을 병발 등급 의 값 으로 설정 한 다음 에 병발 등급 이라는 매개 변 수 를 사용 하지 않 는 다 고 판단 했다.1.8 에서 자 물 쇠 는 더욱 세분 화 되 었 기 때문에 Node 배열 의 모든 아래 표 시 된 헤드 노드 요 소 를 사용 하여 자 물 쇠 를 추가 합 니 다.용량 이 있 는 만큼 자 물 쇠 를 잠 그 는 것 과 같다.또한 확장 을 진행 할 때 일부 데이터 의 접근 이 막 히 지 않도록 지원 한다.
2. 소스 코드 설명
1, 구조 함수
1.1 인삼 없 는 구조 함수
public ConcurrentHashMap() {
    }

기본 구조 함 수 를 사용한다 면 초기 용량 은 기본적으로 16 이다.
1.2 지 정 된 용량, 로드 인자, 병렬 단계 의 구조 함수
이 세 개의 매개 변수의 구조 함수 외 에 또 다른 두 개의 구조 함수 가 있 는데 그것 이 바로:
ConcurrentHashMap (int initialCapacity): 초기 용량 을 지정 합 니 다.
ConcurrentHashMap (int initialCapacity, float loadFactor): 초기 용량 과 로드 인 자 를 지정 합 니 다.
여기 서 세 개의 매개 변수 가 있 는 구조 함수 만 설명 합 니 다.
한 곳 을 주의 하 십시오: 초기 용량 은 로드 인자 와 관련 이 있 습 니 다.
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
        //                       size
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        //            size 2     
        int cap = (size >= (long)MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)size);
        //        
        this.sizeCtl = cap;
    }

2, put 방법
put 방법 에서 puutVal 방법 을 호출 합 니 다.
key 와 value 는 null 일 수 없습니다. 그렇지 않 으 면 이상 을 던 집 니 다.
put 흐름:
  • 키 에 따라 hash 값 계산
  • 노드 배열 이 비어 있 으 면 이 배열 을 초기 화 합 니 다 (초기 화 방법 에서 카 스 조작 보험 을 사용 하여 안전 합 니 다)
  • hash 값 에 따라 배열 아래 표 시 를 계산 합 니 다
  • 이 아래 표 시 된 Node 가 null 이면 새 삽입 을 만 듭 니 다 (CAS 작업 을 사용 하여 병발 안전 을 보장 합 니 다)
  • 만약 에 이 아래 에 노드 요소 가 존재 한다 면 링크 인지 빨 간 검 은 나무 인지 판단 하고 서로 다른 방법 으로 요 소 를 삽입 하거나 교체 합 니 다 (synchronized 동기 코드 블록 을 사용 하여 병행 안전 을 확보 합 니 다)
  • 요 소 를 삽입 한 후에 링크 라면 링크 를 빨 간 검 은 나무 로 바 꿔 야 하 는 지 판단 해 야 한다 (링크 길이 가 8 보다 크 면 빨 간 검 은 나무 로 바 꿔 야 한다)
  • 마지막 으로 노드 배열 의 노드 요 소 를 통계
  • 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   null,        ,   Map    null  
            if (key == null || value == null) throw new NullPointerException();
            //   key   hash 
            int hash = spread(key.hashCode());
            int binCount = 0;
            //  Node         tab  ,       
            for (Node[] tab = table;;) {
                Node f; int n, i, fh;
                //   tab null         
                if (tab == null || (n = tab.length) == 0)
                    //    Node       ,           
                    //                  ,                
                    //         ,       ,       tab  null            
                    tab = initTable();
                // (n - 1) & hash        
                // tabAt            Node      
                //           null,       Node  ,  cas        
                //       ,           ,      
                else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                    if (casTabAt(tab, i, null, new Node(hash, key, value, null)))
                        break;                   // no lock when adding to empty bin
                }
                //      f   ,                  
                else if ((fh = f.hash) == MOVED)
                    tab = helpTransfer(tab, f);//       。
                //   node        ,                ,
                //                             
                else {
                    V oldVal = null;
                    //   synchronized ,           
                    synchronized (f) {
                        //             f            
                        //         ,      ,                  
                        if (tabAt(tab, i) == f) {
                            if (fh >= 0) {
                                binCount = 1;
                                for (Node e = f;; ++binCount) {
                                    K ek;
                                    //    key         ,  if   
                                    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) {
                        //            8,        
                        if (binCount >= TREEIFY_THRESHOLD)
                            treeifyBin(tab, i);//                  
                        //        if,      put     
                        if (oldVal != null)//               
                            return oldVal;
                        break;
                    }
                }
            }
            //         ,                 
            //     node       ,         
            // binCount,   0,         
            addCount(1L, binCount);
            return null;
        }

    2.1 initTable () Node 배열 을 초기 화 하 는 방법
    CAS 원자 로 sizeCtl 의 값 을 수정 합 니 다. 성공 한 스 레 드 를 수정 해 야 초기 화 할 수 있 습 니 다. 한 스 레 드 만 수정 할 수 있 습 니 다. 다른 스 레 드 는 이 값 이 수 정 된 것 을 발견 하면 Node 배열 이 초기 화 될 때 까지 CPU 를 양보 합 니 다.
    private final Node[] initTable() {
            Node[] tab; int sc;
            while ((tab = table) == null || tab.length == 0) {
                //   sizeCtl  0   cpu(              )
                // sizeCtl         ,         sc,sc      
                if ((sc = sizeCtl) < 0)
                    Thread.yield(); // lost initialization race; just spin
                //   cas   sizeCtl     -1,            
                else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        //               ,  table   null,            
                        if ((tab = table) == null || tab.length == 0) {
                            // sc        ,        n,   new ConcurrentHashMap         ,            16
                            int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                            @SuppressWarnings("unchecked")
                            Node[] nt = (Node[])new Node,?>[n];//    Node  
                            table = tab = nt;//  Node     table  
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        //      ,                 。
                        sizeCtl = sc;
                    }
                    break;
                }
            }
            return tab;
        }

    2.2 addCount(long x, int check) node 의 데 이 터 를 통계 하여 확장 이 필요 하 다 면 확장 합 니 다.
    노드 요소 + 1 에 성공 한 후 (fullAddCount 방법 CAS 작업 으로 병발 안전 보장) 현재 노드 요소 개 수 를 계산 합 니 다.
    현재 원소 의 개수 가 용량 한도 값 에 도 달 했 는 지 판단 하고 만약 그렇다면 용량 을 늘린다.
    /**
         *            。
         *      ,           :-1     ,   -(1 +          )。
         *    null ,              ,    0。
         *      ,      。
         */
        private transient volatile int sizeCtl;
    
    
    private final void addCount(long x, int check) {
            CounterCell[] as; long b, s;
            //                             :if  
            if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
                CounterCell a; long v; int m;
                boolean uncontended = true;//      
                // ThreadLocalRandom.getProbe():            
                //                        0                  
                if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                        // CELLVALUE  CounterCell      value 
                        //            +1    
                        !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                    //                  ,    fullAddCount       
                    // uncontended:          false,     ;                   true   
                    fullAddCount(x, uncontended);
                    return;
                }
                if (check <= 1)
                    return;
                s = sumCount();//          
            }
            //        
            if (check >= 0) {
                Node[] tab, nt; int n, sc;
                //           s          sizeCtl ,    sizeCtl    ,    ,
                //                                      
                //   while                                              ,
                //         ,      ,      
                while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) {
                    int rs = resizeStamp(n);
                    if (sc < 0) {// sc < 0           
                        //                        ,         while  
                        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);
                    }
                    //             ,       ,       nextTab   ,      
                    else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2))
                        transfer(tab, null);
                    s = sumCount();
                }
            }
        }
    
    
    // See LongAdder version for explanation
        private final void fullAddCount(long x, boolean wasUncontended) {
            int h;
            if ((h = ThreadLocalRandom.getProbe()) == 0) {
                ThreadLocalRandom.localInit(); //    0            
                h = ThreadLocalRandom.getProbe();//           
                wasUncontended = true;//               
            }
            boolean collide = false;                // True if last slot nonempty
            for (;;) {
                CounterCell[] as; CounterCell a; int n; long v;
                //                  if  
                if ((as = counterCells) != null && (n = as.length) > 0) {
                    if ((a = as[(n - 1) & h]) == null) {//                       if  
                        if (cellsBusy == 0) {            // Try to attach new Cell
                            //        
                            CounterCell r = new CounterCell(x); // Optimistic create
                            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;//  true       
                                    }
                                } finally {
                                    cellsBusy = 0;//       0
                                }
                                if (created)//        created true,    
                                    break;
                                continue;           // Slot is now non-empty
                            }
                        }
                        collide = false;
                    }
                    else if (!wasUncontended)       // CAS already known to fail
                        wasUncontended = true;      // Continue after rehash
                    //             +1            
                    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) {//               
                                CounterCell[] rs = new CounterCell[n << 1];
                                for (int i = 0; i < n; ++i)
                                    rs[i] = as[i];
                                counterCells = rs;
                            }
                        } finally {
                            cellsBusy = 0;//   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;//   0(      )
                    }
                    if (init)
                        break;
                }
                //       +1       
                else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                    break;                          // Fall back on using base
            }

    3, get 방법
  • key 에 따라 hash 를 계산 합 니 다.
  • hash 에 따라 배열 아래 표 시 를 계산 합 니 다
  • 링크 라면 샅 샅 이 찾 고 빨 간 검 은 나무 라면 find 방법 으로 찾 습 니 다
  • 찾 으 면 결 과 를 되 돌려 줍 니 다. 되 돌아 오 는 null
  • 을 찾 지 못 했 습 니 다.
    public V get(Object key) {
            Node[] tab; Node e, p; int n, eh; K ek;
            int h = spread(key.hashCode());//        
            //            null
            if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
                if ((eh = e.hash) == h) {
                    //        key   Node,   node value   
                    if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                        return e.val;
                }
                else if (eh < 0)
                    //      hash   0 ,     node(TreeNode)   key     
                    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;
                }
            }
            //           null
            return null;
        }

    4, size 방법
    sumCount 방법 으로 계산 하기
    Counter Cell 배열 을 엄 격 히 하고 여러 스 레 드 를 Counter Cell 배열 에서 통계 한 데 이 터 를 합 쳐 총 수 를 얻 습 니 다.
    public int size() {
            long n = sumCount();
            return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n);
        }
    
    final long sumCount() {
            CounterCell[] as = counterCells; CounterCell a;
            long sum = baseCount;
            if (as != null) {
                //             ,          
                for (int i = 0; i < as.length; ++i) {
                    if ((a = as[i]) != null)
                        sum += a.value;
                }
            }
            //         
            return sum;
        }

    5, remove 방법
  • key 에 따라 노드 배열 에 대응 하 는 헤더 요 소 를 찾 습 니 다 (찾 지 못 하면 null 로 돌아 갑 니 다)
  • 이 헤드 요소 에 자 물 쇠 를 추가 하여 병발 안전 을 확보 합 니 다
  • 링크 나 레 드 블랙 트 리 에서 이 key 의 node 데 이 터 를 찾 아 데 이 터 를 기록 한 후 삭제 한 다음 에 링크 나 레 드 블랙 트 리 구조
  • 를 유지 합 니 다.
  • 만약 에 빨 간 검 은 나무 가 한 노드 를 삭제 한 후에 node 수량 이 8 보다 적 으 면 빨 간 검 은 나 무 를 링크
  • 로 전환한다.
  • 삭제 에 성공 하면 카운터 에 - 1 조작
  • 마지막 으로 삭 제 된 값 을 되 돌려 줍 니 다
  • final V replaceNode(Object key, V value, Object cv) {
            //   key   hash 
            int hash = spread(key.hashCode());
            for (Node[] tab = table;;) {//    
                Node f; int n, i, fh;
                //   node    ,   hash                ,    null
                if (tab == null || (n = tab.length) == 0 || (f = tabAt(tab, i = (n - 1) & hash)) == null)
                    break;
                //                       ,       node    
                else if ((fh = f.hash) == MOVED)
                    tab = helpTransfer(tab, f);
                else {
                    V oldVal = null;
                    boolean validated = false;
                    //  hash          
                    synchronized (f) {
                        //                    
                        if (tabAt(tab, i) == f) {
                            if (fh >= 0) { //      
                                //               ,    ,      
                                validated = true;
                                for (Node e = f, pred = null;;) {
                                    K ek;
                                    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;
                                            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;
                                }
                            }
                            //       ,   findTreeNode    ,    
                            //        node    8        
                            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;
                                        if (value != null)
                                            p.val = value;
                                        else if (t.removeTreeNode(p))
                                            setTabAt(tab, i, untreeify(t.first));
                                    }
                                }
                            }
                        }
                    }
                    //  key       
                    if (validated) {
                        if (oldVal != null) {
                            if (value == null)
                                //       
                                addCount(-1L, -1);
                            return oldVal;
                        }
                        break;
                    }
                }
            }
            return null;
        }

    대비
    1. 병발 단계
    1.7 에서 segment 배열 을 사용 하여 병발 단계 (기본 값 16) 1.8 에서 Node 배열 의 머리 몇 시 를 잠 금 대상 으로 하고 병발 단 계 는 Node 배열 의 길이, 즉 Map 집합 용량 이다.
    2, 데이터 구조
    1.7 에서 만약 에 하나의 노드 아래 에 여러 개의 데이터 가 대응 하면 이 여러 데이터 의 데이터 구 조 는 링크 로 나타난다.
    1.8 에서 노드 아래 에 표 시 된 데이터 가 여러 개 에 대응 하면 이 여러 데이터 의 수량 이 8 보다 많 으 면 빨간색 과 검은색 나무 이 고 8 보다 적 으 면 배열 이다.
    3. 링크 의 삽입
    1.7 헤드 삽입 법 사용
    1.8 꼬리 삽입 법 사용
    4, 확장
    1.7 의 확장 은 segment 대상 중의 작은 노드 배열 을 확장 하 는 것 이다.
    1.8 의 확장 은 전체 노드 그룹 을 확장 하 는 동시에 다 중 스 레 드 확장 을 지원 하 며 확장 할 때 전체 노드 그룹 을 잠 그 지 않 아 일부 데이터 의 접근 을 지원 할 수 있 습 니 다.

    좋은 웹페이지 즐겨찾기