【Java技术探索】,雷神springboot笔记

本文深入探讨了Java并发集合ConcurrentHashMap,分析了其内部使用Unsafe类和CAS算法实现无锁操作,重点讲解了初始化、扩容方法transfer、put方法以及size相关的统计方法。文章详细阐述了并发扩容的实现,包括单线程和多线程的处理方式,以及如何保证线程安全。此外,还介绍了帮助扩容的helpTransfer方法和将链表转换为树的treeifyBin方法。
摘要由CSDN通过智能技术生成

}

Unsafe与CAS


在ConcurrentHashMap中,随处可以看到Unsafe, 大量使用了Unsafe.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗

这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。

因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的。

unsafe静态块

unsafe代码块控制了一些属性的修改工作,比如最常用的SIZECTL 。 在这一版本的concurrentHashMap中,大量应用来的CAS方法进行变量、属性的修改工作。 利用CAS进行无锁操作,可以大大提高性能。

// 各个字段属性的偏移量,可以通过偏移量+首地址获取到对应的数据对象

private static final sun.misc.Unsafe U;

private static final long SIZECTL;

private static final long TRANSFERINDEX;

private static final long BASECOUNT;

private static final long CELLSBUSY;

private static final long CELLVALUE;

private static final long ABASE;

private static final int ASHIFT;

static {

try {

U = sun.misc.Unsafe.getUnsafe();

Class<?> k = ConcurrentHashMap.class;

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);

}

}

三个核心方法

ConcurrentHashMap定义了三个原子操作,用于对指定位置的节点进行操作。正是这些原子操作保证了ConcurrentHashMap的线程安全。

@SuppressWarnings(“unchecked”)

//获得在i位置上的Node节点

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);

}

  • 利用CAS算法设置i位置上的Node节点。之所以能实现并发是因为他指定了原来这个节点的值是多少

  • 在CAS算法中,会比较内存中的值与你指定的这个值是否相等,如果相等才接受你的修改,否则拒绝你的修改

  • 因此当前线程中的值并不是最新的值,这种修改可能会覆盖掉其他线程的修改结果 有点类似于SVN

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);

}

//利用volatile方法设置节点位置的值

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {

U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);

}

初始化方法initTable


  • 对于ConcurrentHashMap来说,调用它的构造方法仅仅是设置一些参数。

  • 整个table的初始化是在向ConcurrentHashMap中插入元素的时候发生的。

  • 如调用put、computeIfAbsent、compute、merge等方法的时候,调用时机是检查table==null。

  1. 初始化方法主要应用了关键属性sizeCtl 如果这个值 <0,表示其他线程正在进行初始化,就放弃这个操作。

  2. 在这也可以看出ConcurrentHashMap的初始化只能由一个线程完成。

  3. 如果获得了初始化权限,就用CAS方法将sizeCtl置为-1,防止其他线程进入。

初始化数组后,将sizeCtl的值改为0.75 * n,源码如下:

/**

  • Initializes table, using the size recorded in sizeCtl.

*/

private final Node<K,V>[] initTable() {

Node<K,V>[] tab; int sc;

while ((tab = table) == null || tab.length == 0) {

//sizeCtl表示有其他线程正在进行初始化操作,把线程挂起。对于table的初始化工作,只能有一个线程在进行。

if ((sc = sizeCtl) < 0)

Thread.yield(); // lost initialization race; just spin

else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//利用CAS方法把sizectl的值置为-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);//相当于0.75*n 设置一个扩容的阈值

}

} finally {

sizeCtl = sc;

}

break;

}

}

return tab;

}

扩容方法 transfer

  • ConcurrentHashMap容量不足的时候,需要对table进行扩

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值