Чтение исходного кода ConcurrentHashMap
Немного грубая картина
-
ConcurrentHashMap
Он принадлежит к параллельному пакету Java и может быть назван потокобезопасным.HashMap.
(именуемый в дальнейшемCHM
) -
хорошо известно,
HashMap
Имеет хорошую производительность доступа, но не поддерживает параллельные среды,HashTable
Поддержка параллельной среды и прямое добавление метода доступаSynchronized
способ значительно снизит производительность, хотяSynchronize
существуетJDK1.6
После большой оптимизации, но все еще не самое лучшее. -
существует
HashMap
серединаМассив + связанный список/красно-черное деревоПо строению отличается отHashTable
блокирует весь объект массива вConcurrentHashMap
Используя механизм блокировки каждого бакета в массиве, я не знаю, может ли он стать блокировкой сегмента.JDK1.7
Сегмент используется, хотя и сохранен в коде 1.8, он очень короткий и только для совместимости) -
Это только личное мнение, если есть какие-либо ошибки, пожалуйста, оставьте сообщение, спасибо!
Переменные-члены
1. transient volatile Node<K,V>[] table;
Массив узлов, который фактически хранит данные,volatile
Гарантированная видимость.
2. private transient volatile Node<K,V>[] nextTable;
Следующий используемый массив не пуст только при обновлении расширения, и данные будут медленно перемещаться в этот массив во время расширения.
Массив чрезмерно расширен и не может быть доступен вне класса
3. private transient volatile long baseCount;
Статистика элемента при отсутствии конкуренции
4. private transient volatile int transferIndex;
Значение индекса расширения указывает позицию индекса массива таблиц, который был выделен потоку расширения, который в основном используется для координации безопасности параллелизма задач миграции между несколькими потоками.
private transient volatile int sizeCtl;
сопоставимый по важностиAQS
изstate
, является переменной гонки, совместно используемой несколькими потоками, используемой для поддержания различных состояний и сохранения различной информации.
-
sizeCtl > 0
можно разделить на две ситуации:- Когда не инициализирован,
sizeCtl
представляет начальную емкость. - После инициализации он представляет собой порог расширения, т.Длина текущей длины массива*0,75
- Когда не инициализирован,
-
sizeCtl = -1
: Указывает, что он находится в фазе инициализации или расширения. -
sizeCtl < -1
:sizeCtl
предпринять расширениеидентификатор(старшие 16 бит) иКоличество участвующих потоков(младшие 16 бит) хранение-
существует
addCount
а такжеhelpTransfer
В коде метода ,Если вам нужна помощь в масштабировании, CAS будет заменен наsizeCtl+1
-
Когда текущее содержимое расширения завершено и нет перераспределенной области, поток выйдет из расширения, а CAS будет заменен на
sizeCtl-1
-
Node
- Класс узла, который составляет элементы связанного списка и сохраняет пару ключ-значение K/V.
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
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;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
* 为map.get()提供虚拟化支持;在子类中覆盖.
*/
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
// 里面就是普通的链表循环,直到拿到相应的值
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
- а также
ConcurrentHashMap
,HashMap
серединаNode
разница-
val
а такжеnext
использоватьvolatile
Оформление ключевых слов для обеспечения видимости между несколькими потоками. -
hashCode
Подход немного отличается, потому чтоConcurrentHashMap
не поддерживаетсяkey
илиvalue
является значением NULL, поэтому используйте его напрямуюkey.hashCode() ^ val.hashCode()
Пустое суждение пропускается.
-
-
find()
Этот метод используется, чтобы помочь получить элементы за узлом в определенный период времени.Как правило, он вызывается как головной узел ведра для запроса элементов в ведре.
ForwardingNode
-
узел пересылки
-
Этот класс выживает только в фазе расширения и помещается вверху ведра в качестве узла-маркера и указывает на
nextTable
(расширенный промежуточный массив) -
Как видно из конструктора,
ForwardingNode
изhash
Это -1, остальные пустые, это полный вспомогательный класс.
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
// 构造函数中默认以MOVED:-1为Hash,其它为空
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
// 帮助扩容时的元素查找
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
outer: for (Node<K,V>[] tab = nextTable;;) {
Node<K,V> e; int n;
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
for (;;) {
int eh; K ek;
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
if (eh < 0) {
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
else
return e.find(h, k);
}
if ((e = e.next) == null)
return null;
}
}
}
}
-
find()
Метод действительно помогает во время расширенияget
метод для получения элементов в ведре.
Как добавить элементы
public V put(K key, V value) {
return putVal(key, value, false);
}
- По соглашению наиболее уязвимые методы — это методы логической реализации, которые вызываются напрямую.
Конкретный логический метод хранения putVal
/**
* 方法参数:
* 1. key,value 自然不用说就是k/v的两个值
* 2. onlyIfAbsent 若为true,则仅仅在值为空时覆盖
* 返回值:
* 返回旧值,若是新增就为null.
*/
final V putVal(K key, V value, boolean onlyIfAbsent) {
// CHM不支持NULL值的铁证.
if (key == null || value == null) throw new NullPointerException();
// 获得key的Hash,spread可以称之为扰动函数
int hash = spread(key.hashCode());
int binCount = 0;
// 无限循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 在tab为空时负责初始化Table
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 使用`(n-1)&hash`确定了元素的下标位置,获取对应节点
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
}
// 如果获取的桶的头结点的`Hash`为`MOVED`,表示该节点是`ForwardingNode`
// 也就表示数组正在进行扩容
else if ((fh = f.hash) == MOVED)
// 帮助扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 上锁保证原子性,volatile仅能保证可见性
// f为key获取到的节点元素,以此为锁对象
synchronized (f) {
// f在上文就是根据`tabAt(tab,i)`获取的
// 此处是再次获取验证有没有被修改
if (tabAt(tab, i) == f) {
// 与else.if比较,得知
// fh >= 0表示当前节点为链表节点,即当前桶结构为链表 ???
if (fh >= 0) {
// 链表中的元素个数统计
binCount = 1;
// 循环遍历整个桶
// 跳出循环的两种情况:
// 1. 找到相同的值,binCount此时表示遍历的节点个数
// 2. 遍历到末尾,binCount就表示桶中的节点个数
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 源码中大量运用了表达式的短路特性,来展示判断的优先级
// 1. 若hash不相等,则直接跳过判断
// 2. hash相等之后,若key的地址相同,则直接进入if
// 3. 地址不同时在进入判断内容是否相等
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
// onlyIfAbsent为true,表示存在时不覆盖内容
if (!onlyIfAbsent)
e.val = value;
// 已经找到确定的元素了,更新不更新都跳出
break;
}
// 因为e就在同步代码块中,桶已经被上锁,不可能有别的线程改变
// 所以不需要重新获取
Node<K,V> pred = e;
// 1. 如果e为空,则直接将元素挂接到e后面,跳出循环
// 2. e不为空,继续遍历
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 类似HashMap,树节点独立操作.
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) {
// 如果binCount大于树的临界值,就将链表转化为红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// 如果oldVal部位空,则返回
if (oldVal != null)
return oldVal;
break;
}
}
}
// 添加元素计数,并在binCount大于0时检查是否需要扩容
addCount(1L, binCount);
return null;
}
Весь процесс добавления
-
Судя и исключая ключ, значение не пусто,
ConcurrentHashMap
Нулевой ключ или значение не поддерживается. -
Получите возмущенный хэш, введите обход массива вкладок и инициализируйте, если массив пуст.
-
пройти через
(n - 1) & hash
Формула для получения нижнего индекса ведра, если ведро пусто, введите ключ напрямую, а значение является головным узлом ведра. -
Определить хеш головного узла корзины, если
hash == -1
выражатьАссортимент расширяется и помогает расширяться. -
Войти
synchronize
Блок синхронизированного кода, еслиЕсли хэш головного узла корзины больше 0, это означает, что структура корзины представляет собой связанный список., за которым следует обычный обход связанного списка, добавление или перезапись. -
еслиГоловной узел ковша
TreeBin
Тип указывает на то, что структура ведра представляет собой красно-черное дерево., обход которого осуществляется по операции красно-черного дерева. -
Выйдите из блока кода синхронизации и определите статистику во время обхода
binCount
Нужно ли его преобразовать в красно-черную древовидную структуру. -
судить
oldVal
Пустой он или нет, этот шаг тоже очень критичен.Если он не пустой, то операция перезаписывается, прямоreturn
Это нормально, не нужно проверять расширение. -
если
oldVal
Не для кондиционераaddCount
Метод добавляет количество элементов и определяет, требуется ли расширение.
Метод получения элемента
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 获取hash,并进过扰动
int h = spread(key.hashCode());
// 判断以进入获取方法
// 1. 数组不为空 & 数组长度大于0
// 2. 获取的桶不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
// 获取桶下标的公式都是通用的 `(n -1) & h`
(e = tabAt(tab, (n - 1) & h)) != null)
{// 对于桶中头节点的hash,对比成功就不需要遍历整个列表了
if ((eh = e.hash) == h) {
// 返回匹配的元素value
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 元素hash < 0的情况有以下三种:
// 1. 数组正在扩容,Node的实际类型是ForwardingNode
// 2. 节点为树的root节点,TreeNode
// 3. 暂时保留的Hash, Node
// 不同的Node都会调用各自的find()方法
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 如果头节点不是所需节点,且Map此时并未扩容
// 直接遍历桶中元素查找
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
Полный процесс приобретения выглядит следующим образом:
- Получено функцией возмущения
key
Хэш, прежде чем его получить, сначала оценит, пуста ли вкладка и ее длина - пройти через
(n -1)& hash
Полученные сегменты В следующей таблице получены сегменты. - судить
key
Независимо от того, равен ли хэш ведра головному узлу ведра, он будет возвращен напрямую, если они равны. - Если полученный узел головки ковша
hash < 0
,ВыражатьВ следующих трех состояниях фактический тип узла вызывается вызовомfind
метод получения элемента.- Массив расширяется, фактический тип узла
ForwardingNode
- Узел является корневым узлом дерева, а тип узла —
TreeNode
- Временно зарезервированный хеш, узел
- Массив расширяется, фактический тип узла
- Если хэши не равны и хэш головного узла нормальный, тоЭто обычная операция поиска по связному списку.
Механизм расширения
- Я должен сказать, что код части расширения определенно супер-первоклассный мастер!!!
Мониторинг расширения addCount
-
addCount
Роль:- Увеличивать
ConcurrentHashMap
количество элементов - Необходимо ли расширить обнаружение прекурсоров,
- Увеличивать
/**
* 参数:
* x -> 具体增加的元素个数
* check -> 如果check<0不检查时都需要扩容,
*/
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 1. counterCells不为空
// 2. CAS修改baseCount属性成功
if ((as = counterCells) != null ||
// CAS增加baseCOunt
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
// 线程争用的状态标记
boolean uncontended = true;
// 1. 计数cell为null,或长度小于1
// 2. 随机去一个数组位置为为空
// 3. CAS替换CounterCell的value失败
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
// CAS增加CounterCell的value值失败会调用fullAddCount方法
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
// 根据`check >= 0`判断是否需要检查扩容
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 1. 如果元素总数大于sizeCtl,表示达到了扩容阈值
// 2. tab数组不能为空,已经初始化
// 3. table.length小于最大容,有扩容空间
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 根据数组长度获取一个扩容标志
int rs = resizeStamp(n);
if (sc < 0) {
// 如果sc的低16位不等于rs,表示标识符已经改变. // 待补充
// 如果nextTable为空,表示扩容已经结束
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// CAS替换sc值为sc+1,成功则开始扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
// 调用transfer开始扩容,此时nextTable已经指定
transfer(tab, nt);
}
// `sc > 0`表示数组此时并不在扩容阶段,更新sizeCtl并开始扩容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
// 调用transfer,nextTable待生成
transfer(tab, null);
s = sumCount();
}
}
}
помощьПомощь по переводу развернуть
/**
* 参数:
* tab -> 扩容的数组,一般为table
* f -> 线程持有的锁对应的桶的头节点
* 调用地方:
* 1. `putVal`检测到头节点Hash为MOVED
*/
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// 1.参数数组不能为空
// 2.参数f必须为ForwardingNode类型
// 3.f.nextTab不能为空
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// resizeStamp一顿位操作打的我头昏脑涨
// 获取扩容的标识
int rs = resizeStamp(tab.length);
// Map仍处在扩容状态的判断
// 1. 判断节点f的nextTable是否和成员变量的nextTable相同
// 2. 判断传入的tab和成员变量的table是否相同
// 3. sizeCtl是否小于0
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
// 两种不同的情况判断
// 一. 不需要帮助扩容的情况
// 1. sc的高16位不等于rs
// 2. sc等于rs+1
// 3. sc等于rs+MAX_RESIZERS
// 4. transferIndex <= 0, 这个好理解因为扩容时会分配并减去transferIndex,
// 小于0时表示数组的区域已分配完毕
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// 二. CAS `sc+1`并调用transfer帮助扩容.
// 线程在帮助扩容时会对sizeCtl+1,完成时-1,表示标记
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
Основной метод расширения переноса отвечает за миграцию элементов в корзине.
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// stride为此次需要迁移的桶的数目
// NCPU为当前主机CPU数目
// MIN_TRANSFER_STRIDE为每个线程最小处理的组数目
// 1. 在多核中stride为当前容量的1/8对CPU数目取整,例如容量为16时,CPU为2时结果是1
// 2. 在单核中stride为n就为当前数组容量
// !!! stride最小为16,被限定死.
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
// nextTab是扩容的过渡对象,所以必须要先初始化
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
// !!! 重点就在这 扩容后的大小为当前的两倍 --> n << 1
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
// 扩容失败,直接填充int的最大值
sizeCtl = Integer.MAX_VALUE;
// 直接退出
return;
}
// 更新成员变量
nextTable = nextTab;
// transferIndex为数组长度
transferIndex = n;
}
// 记录过渡数组的长度
int nextn = nextTab.length;
// 此处新建了一个ForwardingNode用于后续占位
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
/**
* 以上为数据准备部分,初始化过渡数组,记录长度,创建填充节点等操作
* 以下时真正扩容的主要逻辑
*/
// 该变量控制迁移的进行,
boolean advance = true;
boolean finishing = false; // 两个变量作用未知 finishing可能是此次扩容标记
// 扩容的for循环里面可以分为两部分
// 一. while循环里面确定需要迁移的桶的区域,以及本次需要迁移的桶的下标
// 这个i就是需要迁移的桶的下标
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// 该while代码块根据if的顺序功能分别是
// --i: 负责迁移区域的向前推荐,i为桶下标
// nextIndex: 在没有获取负责区域时,检查是否还需要扩容
// CAS: 负责获取此次for循环的区域,每次都为stride个桶
while (advance) {
int nextIndex, nextBound;
// 这个`--i`每次都会进行,每次都会向前推进一个位置
if (--i >= bound || finishing)
advance = false;
// 因此如果当transferIndex<=0时,表示扩容的区域分配完
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
// CAS替换transferIndex的值,新值为旧值减去分到的stride
// stride就表示此次的迁移区域,nextIndex就代表了下次起点
// 从这里可以看出扩容是从数组末尾开始向前推进的
}else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
// bount为此次扩容的推进终点,下次起点
bound = nextBound;
// i此次扩容开始的桶下表
i = nextIndex - 1;
advance = false;
}
}
// 二. 扩容的逻辑代码
// 1. 此if判定扩容的结果,中间是三种异常值
// 1). i < 0的情况时上面第二个if跳出的线程
// 2). i > 旧数组的长度
// 3). i+n大于新数组的长度
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 此阶段扩容结束后的操作
// 1. 将nextTable置空,
// 2. 将中间过渡的数组赋值给table
// 3. sizeCtl变为1.5倍(2n-0.5n)
if (finishing) {
nextTable = null;
table = nextTab;
// 分别使用有符号左移,无符号右移
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// CAS替换`sizeCtl-1`,表示本线程的扩容任务已经完成
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 表达式成立表示还有别的线程在执行扩容,直接退出
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 表达式成立,表示已经全部扩容完成.
finishing = advance = true;
// 提交前重新检查
i = n;
}
}
// 2. 扩容时发现负责的区域有空的桶直接使用ForwardingNode填充
// ForwardingNode持有nextTable的引用
else if ((f = tabAt(tab, i)) == null)
// CAS替换
advance = casTabAt(tab, i, null, fwd);
// 3. 表示处理完毕
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
// 4. 迁移桶的操作
else {
// sync保证原子性和可见性
synchronized (f) {
// 获取数组中的第i个桶的头节点
// 进入synchronized之后重新判断,保证数据的正确性没有在中间被修改
if (tabAt(tab, i) == f) {
// 此处扩容和HashMap有点像,分为了lowNode和highNode两个头结点
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);
// true的话会重新
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;
}
}
}
}
}
}
Расширение запускает логику потока
-
существует
addCount
В середине метода проверяем, достигает ли количество элементов порога раскрытия (0,75 * table.length), если оно превышает, срабатывает раскрытие и вызывается методtransfer
.- Обратите внимание, что в это время
sizeCtl
БудетCAS
заменить(resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2
- Обратите внимание, что в это время
-
Далее
teansfer
код:-
согласно с
CPU
Рассчитайте размер выделенной области для каждого расширения с текущей емкостью, минимум 16, что выражается какstride
. -
Если переходный массив
nextTab
Если он не инициализирован, сначала инициализируйте массив.transferIndex
Запишите длину старого массива как длину расширения. -
Данные, необходимые для вышеуказанного расширения, полностью подготовлены для запуска конкретной операции расширения:
-
в
while
Диапазон ведер, входящих в это расширение, получается в цикле, т. е.[transferIndex,transferIndex-stride]
спектр,i
Указывает индекс расширяемого в данный момент сегмента. -
Три суждения, четыре фрагмента кода для выполнения операции в разных ситуациях соответственно
-
i
аномальное значение,< 0 || >= n || + n > nextn
, указывая на то, что расширение завершено, иwhile
Задачи расширения, которые не назначены в цикле.-
если в это время
finishing
Параметрыtrue
Указывает, что общее расширение завершено, и проверка перед завершением завершена. -
если
finishing
заfalse
,но**CAS
Замените sizeCtl на sizeCtl-1**, указывая на то, что поток завершил задачу расширения и должен выйти.После успешной замены он также проверит
sc
Равен ли онaddCount
Значение, когда оно приходит, если оно не равно, оно напрямуюreturn
, указывая на то, что еще есть потоки, не завершившие задачу расширения.
-
-
i
Соответствующее ведро пусто, используйте его напрямуюForwardingNode
Заполните головной узел, указав, что емкость здесь расширяется.advance
заtrue
-
Если узел проверен
hash
заMoved
Указывает, что текущий узелForwardingNode
,advance
заtrue
. -
Вышеупомянутые три случая исключены, то есть миграция соответствующего бакета, и
HashMap
вроде как поставил после концаadvance
заtrue
-
-
Затем мы вернемся к шагу 4.
-
Логика масштабирования ведомого потока
- существует
putVal
В способе работы других элементов установлено, что полученный узел головки ковша являетсяForwdingNode
означаетConcurrentHashMap
В настоящее время он расширяется и будет немедленно вызванhelpTransfer
Помогите расширить. -
helpTransfer
Будут различные суждения о правильности, и только при выполнении следующих трех условий это поможет расшириться.-
tab
не пусто - Является ли головной узел
ForwardingNode
- массив переходов
nextTable
Инициализировать ли.
-
- Цикл while имеет следующие два суждения
- Чтобы определить, нуждается ли процесс расширения в помощи, существуют следующие пять ситуаций, в которых помощь не требуется.
-
sc >> 16 != rs
- Идентификатор изменился. -
sc == rs+1
- Поток, вызвавший расширение, завершился, и расширение было завершено. -
sc == rs+MAX_RESIZER
- Количество потоков, участвующих в расширении, достигает максимального значения -
transferIndex <= 0
- Площадь расширения была выделена
-
- За исключением вышеперечисленных ситуаций, не требующих помощи, вызовет
transfer
Помогите расширить.
- Чтобы определить, нуждается ли процесс расширения в помощи, существуют следующие пять ситуаций, в которых помощь не требуется.
Изменения sizeCtl во время расширения
-
addCount
->sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2
- Вот момент, который я давно выяснял: почему в
helpTransfer
будет судитьsizeCtl
16-битные операции высокого порядка, - Присвоение значения здесь эквивалентно присвоению
resizeStamp(n)
Значение увеличивается на 16 бит и присваиваетсяsizeCtl
, а младшие 16 бит экономят эти 2. То есть при расширении разрядностиsizeCtl
Старшие 16 бит содержат идентификатор, а младшие 16 бит содержат количество участвующих потоков. - Какой чертов гений.
- Вот момент, который я давно выяснял: почему в
-
Есть темы, участвующие в расширении ->
sizeCtl = sizeCtl - 1
-
Расширение выхода из потока ->
sizeCtl = sizeCtl + 1
-
Расширение завершено ->
sizeCtl = nextTab.length * 0.75
метод инициализации
- а также
HashMap
Такой же,ConcurrentHashMap
Вместо того, чтобы инициализировать базовый массив непосредственно в конструкторе,put
В методе хранения ожидания определите, требуется ли расширение.
Функция инициализации массива initTable
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// `sizeCtl`表示有别的数组正在初始化,让出CPU时间
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// CAS操作,以-1置换`sizeCtl`的值
// 可以看出 `sizeCtl==-1`时,表示数组正在某个线程初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 置换之后需要重新检测数组是否未初始化
if ((tab = table) == null || tab.length == 0) {
// sc就是置换之前的sizeCtl.
// 此时sizeCtl作为初始容量.
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 初始化结束之后sc变为0.75n,是扩容阈值
sc = n - (n >>> 2);
}
} finally {
// 为避免异常退出导致sizeCtl永久为-1,此处强制赋值.
sizeCtl = sc;
}
break;
}
}
// 返回了新建的数组地址
return tab;
}
-
initTable
метод вputVal
а не функция-конструкторCHM
Ленивый механизм загрузки в . -
Процесс инициализации:
- Проверьте, инициализируются ли другие потоки, откажитесь от кванта времени, если он есть, и перейдите к следующему шагу, если нет.
- Перед инициализацией установите
sizeCtl
пройти черезCAS
Заменено на -1, что указывает на то, что он инициализируется - к
sizeCtl
Предыдущее значение — начальная емкость,sizeCtl
- инициализация завершена,будет
sizeCtl
Назначение составляет 0,75*емкость массива.(sizeCtl проходит через всю статью, это действительно важно)
общий инструментальный подход
1. resizeStamp Получает отметку при расширении
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
-
Integer.numberOfLeadingZeros(n)
Возвращает количество ведущих нулей в 32-битной двоичной форме числа n., например значение бита 16int(32位)
Бинарный тип представлен как000000...0010000
, перед 1 стоит 27 нулей, а в конце 27. -
|
Теперь операцию можно понимать здесь просто как сложение. - Эффект интеграции:Получите количество нулей перед значащими битами n плюс 1 в 15-й степени.
- Непонятно, зачем вы хотите получить штамп
2. Распределенная функция возмущения
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
- функция возмущения и
HashMap
серединаhash()
Аналогично действует метод. -
CHM
Помимо операции XOR между старшими 16 битами и младшими 16 битами, функция возмущения в HASH_BITS,Это может эффективно снизить вероятность столкновения хэшей и сделать распределение элементов более равномерным.
Метод доступа к элементам массива узлов
1. tabAt получает элементы массива изменчивым способом
@SuppressWarnings("unchecked")
// tab: 数组 i : 下标
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
2. КАСТАБАТCAS
Форма заменяет элементы массива
// tab: 原始数组 i:下标 c:对比元素 v:替换元素
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
3. setTabAt обновляет элементы массива энергозависимым образом.
// tab:原始数组 i:下标 v:替换元素
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
Небезопасный статический блок
-
Unsafe
Это область, которую разработчики Java редко затрагивают, но вот краткое понимание
private static final sun.misc.Unsafe U;
// sizeCtl属性的偏移地址
private static final long SIZECTL;
// transferIndex属性的偏移地址
private static final long TRANSFERINDEX;
// baseCount的偏移地址
private static final long BASECOUNT;
// cellsBusy的偏移地址
private static final long CELLSBUSY;
// CounterCell类中value的偏移地址
private static final long CELLVALUE;
// Node数组第一个元素的偏移地址
private static final long ABASE;
// Node数组中元素的增量地址,与ABASE配合使用能访问到数组的各元素
private static final int ASHIFT;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
// 先通过反射获取到对应的属性值,再通过Unsafe类获取属性的偏移地址
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex"));
BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset
(k.getDeclaredField("cellsBusy"));
Class<?> ck = CounterCell.class;
CELLVALUE = U.objectFieldOffset
(ck.getDeclaredField("value"));
Class<?> ak = Node[].class;
// 获取数组中第一个元素的偏移地址
ABASE = U.arrayBaseOffset(ak);
// 获取数组的增量地址
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}