【笔记】Java并发编程之美-Java并发包中原子操作类原理剖析

原子变量操作类AtomicLong

JUC并发包中包含有Atomiclnteger、AtomicLong和AtomicBoolean等原子性操作类,它们的原理类似,本章讲解AtomicLong类。AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现。
AtomicLong类就是通过BootStarp类加载器进行加载的,故可以通过Unsafe.getUnsafe()方法获取到Unsafe类的实例。

递增和递减操作代码

都是通过调用Unsafe的getAndAddLong方法来实现操作,这个函数是个原子性操作,这里第一个参数是AtomicLong实例的引用,第二个参数是value变量在AtomicLong中的偏移值,第三个参数是要设置的第二个变量的值。

booleancompareAndSet(longexpect,longupdate)方法

内部还是调用了unsafe.compareAndSwapLong方法。如果原子变量中的value值等于expect,则使用update值更新该值并返回true,否则返回false。

JDK8新增的原子操作类LongAdder

使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,而这会白白浪费CPU资源。

所以LongAdder,是在内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,这样,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。

LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。

使用@sun.misc.Contended注解对Cell类进行字节填充,这防止了数组中多个元素共享一个缓存行。

LongAdder代码分析

(1)LongAdder的结构是怎样的?
public class LongAdder extends Striped64 implements Serializable
LongAdder 类继承自 Striped64 类,在 Striped64 内部维护着三个变量。 LongAdder 的真实值其实是 base 的值与 Cell 数组里面所有 Cell 元素中的 value 值的累加, base 是个基础值,默认为 0。 cellsBusy 用来实现自旋锁,状态值只有 0 和1 ,当创建 Cell 元素, 扩容 Cell 数组或者初始化 Cell 数组时,使用 CAS 操作该变量来保证同时只有一个线程可 以进行其中之一的操作。

(2)当前线程应该访问Cell数组里面的哪一个Cell元素?
是通过 getProbe() & m 进行计算的,其中 m 是当前 cells 数组元素个数 -1 , getProbe()则用于获取当前线程中变量 threadLocalRandomProbe 的值,这个值一开始为 0,在代码longAccumulate(x, null, uncontended)里面会对其进行初始化。

(3)如何初始化Cell数组?

//初始化Cell数组 
else if (cellsBusy == 0 && cells ==as && casCellsBusy()) { 
	boolean init = false; 
	try { 
		if (cells == as) {
			Cell[] rs = new Cell[2] ; 
			rs[h & l] = new Cell(x) ; 
			cells = rs; 
			init = true; 
		}
	}finally { 
		cellsBusy = 0;
	} 
	if(init) break;
} 

(4)Cell数组如何扩容?
当当前元素没有达到CPU个数并且有冲突(多个线程访问了同一元素)则扩容。扩容操作也是先通过 CAS 设置 cellsBusy 为 1 ,然后才能进行扩容。 假设 CAS 成功则执行代码

Cell[] rs = new Cell[n << 1];	
for(int i = 0; i < n; ++i ) rs[i] = as [i]; 
cells = rs;

将容量扩充为之前的 2 倍,并复制 Cell 元素到扩容后数组。 另外,扩容后 cells 数组里面除了包含复制过来的元素外,还包含其他新元素,这些元素的值目前还是 null。

(5)线程访问分配的Cell元素有冲突后如何处理?
对 CAS 失败的线程重新计算当前线程的随机值 threadLocalRandomProbe, 以减少下次访问 cells 元素时的冲突机会。

(6)如何保证线程操作被分配的Cell元素的原子性?
当前线程通过分配的 Cell 元素的 cas 函数,通过 CAS 操作,保证了当前线程更新时被分配的 Cell 元 素中 value 值的原子性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值