ConcurrentHashMap 분석:예열(내부 작은 방법 분석)

앞의 글 에서 HashMap 의 주요 구성원 속성,내부 클래스 와 구조 함수 을 소개 했다.
다음은 HashMap 멤버 들 의 방법 을 정식 적 으로 분석 하기 전에 내부 클래스 의 글자 방법 함 수 를 분석 합 니 다.
먼저 Concurrent HashMap 내부 클래스 Node 의 hash 멤버 속성 값 을 계산 하 는 방법 spread(int h)을 살 펴 보 겠 습 니 다.

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;//       spread(int h)      ~
    final K key;
    volatile V val;
    volatile Node<K,V> next;
    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }
    ...
}
1.spread(int h)방법

/**
 *   Node  hash    :  h hash 
 * eg: 
 * h     --> 	 		 		 1100 0011 1010 0101 0001 1100 0001 1110
 * (h >>> 16) -->  					0000 0000 0000 0000 1100 0011 1010 0101 
 * (h ^ (h >>> 16)) -->				1100 0011 1010 0101 1101 1111 1011 1011
 *  :(h ^ (h >>> 16))     h  16        ,    hash    ,  hash    ~
 * ------------------------------------------------------------------------------
 * HASH_BITS -->					0111 1111 1111 1111 1111 1111 1111 1111
 * (h ^ (h >>> 16)) -->				1100 0011 1010 0101 1101 1111 1011 1011
 * (h ^ (h >>> 16)) & HASH_BITS --> 0100 0011 1010 0101 1101 1111 1011 1011
 *  : (h ^ (h >>> 16))      & HASH_BITS,         hash          
 */
static final int spread(int h) {
    //     hash   ^  hash    16 ,  & HASH_BITS(0x7fffffff:     31 1)
    return (h ^ (h >>> 16)) & HASH_BITS;
}
다음은 tabAt(Node[]tab,int i)방법 을 소개 합 니 다.tab(Node[])배열 을 가 져 와 아래 표 시 된 Node 노드 를 지정 합 니 다.
2,tabAt 방법

/**
 *    tab(Node[])        i  Node  
 * Node<K,V>[] tab:  Node[]  
 * int i:      
 */
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    // ((long)i << ASHIFT) + ABASE    :         ~
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
((long)i << ASHIFT) + ABASE 을 분석 할 때 먼저 이전 글 을 복습 합 니 다:Concurrent HashMap 소스 코드 분석01 구성원 속성,내부 유형,구조 방법 분석 에서 소개 한 일부 속성 필드 의 의미:

// Node   class  
Class<?> ak = Node[].class;
// U.arrayBaseOffset(ak):  as  Node[]            ABASE
ABASE = U.arrayBaseOffset(ak);
// scale:                  , scale  Node[]              
int scale = U.arrayIndexScale(ak);// scale   2    
// numberOfLeadingZeros(scale)   scale,             ,          ,      0     :eg, 8     =>1000   numberOfLeadingZeros(8)     28,    ?  Integer 32 ,1000 4 ,      32-4 0,      0    28 
// 4     =>100   numberOfLeadingZeros(8)     29
// ASHIFT = 31 - Integer.numberOfLeadingZeros(4) = 2   ASHIFT       ?             :
//      5 Node[]         (    ):       scale     ASHIFT = 2
// ABASE + (5 << ASHIFT) == ABASE + (5 << 2) == ABASE + 5 * scale(       ),       5          
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
위의 몇 가지 속성 필드 의 복습 소 개 를 통 해 얻 기 어렵 지 않 습 니 다.((long)i << ASHIFT) + ABASE 은 현재 Node[] 배열 에서 i 로 표 시 된 노드 대상 의 오프셋 주 소 를 얻 는 것 이다.
그 다음 에 (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE) 방법 을 통 해 Node[] Node 두 개의 매개 변수 에 따라 i 로 표 시 된 Node 노드 대상 을 얻 었 다.
이렇게 돌아 가 는 것 보다 못 하지만 오프셋 주소 에 따라 배열 요 소 를 찾 는 효율 이 높 습 니 다~
3,casTabAt 방법

/**
 *   CAS     Node      i     ,      true,    false
 * Node<K,V>[] tab:  Node[]  
 * int i:      
 * Node<K,V> c:     
 * Node<K,V> v:       
 */
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    //   Unsafe         Node[]          ,    :
    // tab:Node[]  
    // ((long)i << ASHIFT) + ABASE:   i        
    // c:     
    // v:       
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
4.setTabAt 방법

/**
 *       i,  Node[]            :
 * Node<K,V>[] tab:  Node[]  
 * int i:      
 * Node<K,V> v:       
 */
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    // ((long)i << ASHIFT) + ABASE:   i        
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
5.resizeStamp 방법

/**
 * table     ,          ,        ,                   :
 */
static final int resizeStamp(int n) {
    // RESIZE_STAMP_BITS:   16,     ,                     
    return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
=
예 를 들 어 분석 해 보 자.
우리 가 table 용량 이 16 라 이브 러 리 에서 32 까지 필요 할 때:Integer.numberOfLeadingZeros(16) 을 얻 을 수 있 습 니 다.어떻게 얻 었 습 니까?
  • numberOfLeadingZeros(n) 은 들 어 오 는 n 에 따라 현재 수 치 를 2 진법 으로 전환 한 후에 높 은 위치 에서 지위 까지 통 계 를 시작 하여 몇 개의 0 연속 이 한 조각 에 있 는 지 통계 했다.
  • eg:16 바 이 너 리 전환=>1 0000 은 numberOfLeadingZeros(16)의 결 과 는 27 이다.Integer 은 32 위,1 0000 이 5 위 를 차지 하기 때문에 앞 에 (32 - 5) 개 0,즉 가장 긴 0 개 수 는 27 개 이다.
  • (1 << (RESIZE_STAMP_BITS - 1)):RESIZE_STAMP_BITS 은 고정 치 16 으로 확장 과 관련 이 있 으 며 확장 을 계산 할 때 이 속성 치 에 따라 확장 표지 도장 을 생 성 한다.
  • 다음은 계산 해 보 겠 습 니 다.
    
    //  16   32
    16 -> 32 
    //  A  :
    numberOfLeadingZeros(16) => 1 0000 => 27 => 0000 0000 0001 1011
    //  B  :
    (1 << (RESIZE_STAMP_BITS - 1)) => (1 << (16 - 1) => 1000 0000 0000 0000 => 32768
    // A | B 
    Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)) 
    -----------------------------------------------------------------
    0000 0000 0001 1011  ---> A
    1000 0000 0000 0000  ---> B
    -------------------  ---> |                                 
    1000 0000 0001 1011  --->          
    
    6.tableSize For 방법
    
    /**
     *   c,        c ,  2    ,    HashMap      ~
     * eg:c = 28 ,            32
     * c = 28 ==> n=27 => 0b 11011
     *
     * 11011 | 01101 => 11111 ---- n |= n >>> 1
     * 11111 | 00111 => 11111 ---- n |= n >>> 2
     * ....
     * => 11111 + 1 = 100000 = 32
     */
    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;
    }
    
    7.구조 방법(복습)
    이전 글 에서 HashMap 의 구조 방법 을 복습 합 니 다.
    
    //     
    public ConcurrentHashMap() {
    }
    //        
    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                MAXIMUM_CAPACITY :
                tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        /**
         * sizeCtl > 0
         *    table     ,sizeCtl       
         */
        this.sizeCtl = cap;
    }
    //     Map      
    public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
        // sizeCtl        
        this.sizeCtl = DEFAULT_CAPACITY;
        putAll(m);
    }
    //        ,     
    public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
    }
    //        ,     ,    
    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //          initialCapacity      concurrencyLevel 
        if (initialCapacity < concurrencyLevel) 
            //                。
            //  ,JDK1.8              
            initialCapacity = concurrencyLevel; 
        //             ,   size
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        //   size           
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
                MAXIMUM_CAPACITY : tableSizeFor((int)size);
        /**
         * sizeCtl > 0
         *    table     ,sizeCtl       
         */
        this.sizeCtl = cap;
    }
    
    8.총화
    예열 이 끝나 면 다음 글 은 맵 을 병행 하 는 데 중점 을 둔 곳 입 니 다.즉 put()기록 작업 원리~
    저희 의 다른 콘 텐 츠 에 도 많은 관심 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기