JDK 1.8 의 Concurrent HashMap 소스 분석
30597 단어 JDK1.8ConcurrentHashMap소스 코드
1.소스 코드 분석
jdk 8 의 Concurrent HashMap 에는 모두 5 개의 구조 방법 이 있 는데 이 네 가지 구조 방법 중 내부 의 배열 을 초기 화하 지 않 고 일부 변수의 초기 값 만 처리 했다.
jdk 8 의 Concurrent HashMap 의 배열 초기 화 는 요 소 를 처음 추가 할 때 완 료 됩 니 다.
// , , 16
public ConcurrentHashMap() {
}
// ,ConcurrentHashMap 2
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));// + + 1
this.sizeCtl = cap;
}
이 방법 을 사용 하면 초기 용량 은 HashMap 및 jdk 7 의 Concurrent HashMap 과 다 릅 니 다.2 의 멱 제곱 수 를 전달 하 더 라 도 이 방법 으로 계 산 된 초기 용량 은 이 값 보다 큰 2 의 멱 제곱 수 입 니 다.
//
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
// , 2
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);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
// Map , ConcurrentHashMap
// 16
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
2sizeCtl
의미 해석주의:상기 구조 방법 에서 모두 하나의 변수 와 관련된다
sizeCtl
이 변 수 는 매우 중요 한 변수 이 고 매우 풍부 한 의 미 를 가진다.그의 값 이 다 르 고 대응 하 는 의미 도 다르다.여기 서 우 리 는 먼저 이 변수의 서로 다른 값 의 의 미 를 설명 하고 후속 소스 코드 분석 과정 에서 더 설명 한다.4.567914.0 으로 배열 이 초기 화 되 지 않 았 고 배열 의 초기 용량 은 16 이다.
4.567914.정수 입 니 다.배열 이 초기 화 되 지 않 으 면 배열 의 초기 용량 을 기록 합 니 다.만약 에 배열 이 초기 화 되 었 다 면 배열 의 확장 한도 값 을 기록 합 니 다.
4.567914.-1 은 배열 이 초기 화 되 고 있 음 을 나타 낸다.
4.567914.0 보다 작고-1 이 아니 라 배열 이 확장 되 고 있 음 을 나타 낸다.-(1+n)이때 n 개의 스 레 드 가 공동으로 배열 의 확장 작업 을 완성 하고 있 음 을 나타 낸다.
3.기타 속성 의미
전체 해시 표를 대표 합 니 다.
transient volatile Node<K,V>[] table;
해시 표 확장 에 사용 되 며 확장 이 완료 되면 null 로 초기 화 됩 니 다.
private transient volatile Node<K,V>[] nextTable;
baseCount 는 counter Cells 와 함께 전체 해시 표 에 저 장 된 모든 노드 의 갯 수 를 합 쳐 저장 하고 있 습 니 다.
private transient volatile long baseCount;
private transient volatile CounterCell[] counterCells;
2.안전 추가1.소스 코드 분석
1.1 요소 put/putVal 추가 방법
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// ,
if (key == null || value == null) throw new NullPointerException();
// key hash , , hash , 7FFFFFFF , hash
int hash = spread(key.hashCode());
// , 8 ( table >=64),
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// ,
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// hash , cas
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// cas+ ( for ),
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// hash hash MOVED(-1), ,
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
// hash , ,
V oldVal = null;
// , ,
synchronized (f) {
if (tabAt(tab, i) == f) {
//
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// ( key )
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;
}
}
}
}
if (binCount != 0) {
// >=8,
if (binCount >= TREEIFY_THRESHOLD)
// ( table ,>=64 , )
treeifyBin(tab, i);
// ,
if (oldVal != null)
return oldVal;
break;
}
}
}
// , ,
addCount(1L, binCount);
return null;
}
상기 소스 코드 를 통 해 우 리 는 요 소 를 추가 해 야 할 때 현재 요소 에 대응 하 는 통 위치 에 대해 잠 금 작업 을 하 는 것 을 볼 수 있다.이런 한편,요소 가 추 가 될 때 다 중 스 레 드 의 안전 을 확보 하 는 동시에 특정한 통 위치 에 자 물 쇠 를 추가 하 는 것 은 다른 통 위치 에 영향 을 주지 않 고 다 중 스 레 드 의 병행 효율 을 향상 시 키 는 것 을 볼 수 있다.1.2,배열 초기 화,initTable 방법
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// cas+ , ,
while ((tab = table) == null || tab.length == 0) {
// sizeCtl (-1) 0, , cpu
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// cas sizeCtl -1, , , ,
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// double checking,
if ((tab = table) == null || tab.length == 0) {
// sizeCtl 0, 16, sizeCtl
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
// ,
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// , sc
// n , ,sc
// n >>> 2 n / 4
// n - (n >>> 2) n - n / 4 = n * 0.75, 0.75
sc = n - (n >>> 2);
}
} finally {
// , sizeCtl
sizeCtl = sc;
}
break;
}
}
return tab;
}
2.도해2.1 put 자물쇠 도해
3.확장 안전
1.소스 코드 분석
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// cpu, , 16
// cpu,
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
// , null
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
//
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;
// , ( hash -1――MOVED)
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;
// i
// bound
// --i >= bound
if (--i >= bound || finishing)
advance = false;
// -- 1,
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// , transferIndex
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// , if
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// , , , sizeCtl
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// 1
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
}
}
// , fwd
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
//
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
// , ,
// hashmap
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;
}
}
}
}
}
}
2.도해4.다 중 스 레 드 확장 효율 개선(확장 협조)
다 중 스 레 드 가 확장 을 돕 는 작업 은 두 곳 에서 실 행 됩 니 다.
① 요 소 를 추가 할 때 추 가 된 요소 가 사용 하 는 통 위 치 를 fwd 노드 로 발견 하면 확대 에 협조 한 다음 에 요 소 를 추가 합 니 다.
② 요 소 를 추가 한 후에 현재 요소 의 개수 가 확장 한도 값 에 이 르 렀 다 고 판단 합 니 다.이때 sizeCtl 의 값 이 0 보다 적 고 새 배열 이 비어 있 지 않 습 니 다.이때 확장 에 협조 합 니 다.
하나의 스 레 드 가 확장 을 도 울 때마다 sc 는+1,하나의 스 레 드 확장 이 끝 날 때 sc 는-1,sc 가 다시 돌아 올 때(rs<
1.1 요소 가 추가 되 지 않 았 을 때 먼저 용량 을 확대 하 는 것 을 협조 하고 용량 을 확대 한 후에 요 소 를 추가 합 니 다.
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;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
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
}
// fwd , , ,
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
// , null nextTab
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
1.2 먼저 요 소 를 첨가 한 다음 에 확대 에 협조 한다.
private final void addCount(long x, int check) {
//
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);
// sizeCtl 0, ,
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();
}
}
}
메모:확장 코드 는 모두 transfer 방법 에 있 습 니 다.여 기 는 더 이상 군말 하지 않 습 니 다.2.도해
5.집합 길이 의 누적 방식
1.소스 코드 분석
1.1,addCount 방법
① CounterCell 배열 이 비어 있 지 않 고 배열 의 CounterCell 기록 수량 을 우선 활용 합 니 다.
② 배열 이 비어 있 으 면 baseCount 를 누적 하려 고 시도 합 니 다.실패 하면 fullAddCount 논 리 를 실행 합 니 다.
③ 요소 추가 작업 이 라면 확장 이 필요 한 지 여 부 를 계속 판단 한다.
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// CounterCell , CounterCell
// baseCount , CounterCell
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
//
boolean uncontended = true;
// as
// as 0
// as
// as ,
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// , , uncontended false
fullAddCount(x, uncontended);
return;
}
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);
// sc 0, ,
if (sc < 0) {
// null ,
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// 1, ,
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
// ,newTable null
transfer(tab, nt);
}
// , , sizeCtl
// 1+1=2 --》
// rs << RESIZE_STAMP_SHIFT)
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
// ,newTable null
transfer(tab, null);
s = sumCount();
}
}
}
1.2,fullAddCount 방법① CounterCell 배열 이 비어 있 지 않 을 때 CounterCell 배열 의 CounterCell 에 대한 value 누적 을 우선 합 니 다.
② CounterCell 배열 이 비어 있 으 면 CounterCell 배열 을 만 듭 니 다.기본 길 이 는 2 이 고 배열 에 있 는 CounterCell 의 value 를 누적 합 니 다.
③ 배열 이 비어 있 고 다른 스 레 드 가 배열 을 만 들 고 있 을 때 baseCount 를 누적 하여 성공 하면 되 돌아 갑 니 다.그렇지 않 으 면 자전 합 니 다.
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// hash
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
// , null, true
boolean collide = false; // True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
// , CouterCell value
if ((as = counterCells) != null && (n = as.length) > 0) {
// null
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
// CounterCell
CounterCell r = new CounterCell(x); // Optimistic create
// CAS cellBusy 1, CounterCell
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
// , CounterCell
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;
// CounterCell ,
continue; // Slot is now non-empty
}
}
collide = false;
}
// , hash ,
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// hash , , value
//
//
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
// , cpu , hash ,
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
// , , hash,
else if (!collide)
collide = true;
// CounterCell cpu ,
//
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);
}
// CounterCell , , ,
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;
}
// , , baseCount , ,
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
2.도해fullAddCount 방법 에서 as 배열 이 비어 있 지 않 은 논리 도해
6.집합 길이 획득
1.소스 코드 분석
1.1 size 방법
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
1.2 sumCount 방법
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
// baseCount
long sum = baseCount;
if (as != null) {
// CounterCell , CounterCell value
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
주의:이 방법 은 스 레 드 가 안전 한 것 이 아 닙 니 다.7.get 방법
이것 은 아주 간단 합 니 다.hash 값 을 얻 은 다음 에 존재 여 부 를 판단 하고 링크 를 옮 겨 다 니 면 됩 니 다.get 은 잠 금 동작 이 없 음 을 주의 하 십시오!
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// key hash
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) { // 0 key
if ((eh = e.hash) h) { // hash key hash
if ((ek = e.key) key || (ek != null && key.equals(ek))) //
//
return e.val;
}
else if (eh < 0) // TreeBin hash = -2
// ,
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) { // hash 0
if (e.hash h &&
((ek = e.key) key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
JDK 1.8 의 Concurrent HashMap 소스 분석 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 관련 JDK 1.8 Concurrent HashMap 소스 코드 내용 은 저희 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 읽 어 주시 기 바 랍 니 다.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Ubuntu 15 번 JDK 1.8 튜 토리 얼 설치동 의 를 클릭 하고 해당 버 전 을 선택 하여 다운로드 합 니 다. 다운로드 후 명령 을 통 해 가방 을 해제 합 니 다. 이 디 렉 터 리 에 다운로드 한 가방 을 내 려 놓 을 수 있 도록 디 렉 터 리/usr...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.