文章目录
- 概述
- 原子变量操作类
- 主要方法
- incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement
- boolean compareAndSet(long expect, long update)
- 小Demo
- 小结
概述
JUC包提供了一系列的原子性操作类,这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作这在性能上有很大提高。
由于原子性操作类的原理都大致相同,我们以AtomicLong类的实现原理为例,并探讨JDK8新增的 LongAdder和LongAccumulator类的原理
原子变量操作类
JUC并发包中包含有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作类
AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现,我们看下面的代码
代码语言:javascript复制package java.util.concurrent.atomic;
import java.util.function.LongUnaryOperator;
import java.util.function.LongBinaryOperator;
import sun.misc.Unsafe;
/**
* @since 1.5
* @author Doug Lea
*/
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
// 1 获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 2 存放变量的偏移量
private static final long valueOffset;
/**
* Records whether the underlying JVM supports lockless
* compareAndSwap for longs. While the Unsafe.compareAndSwapLong
* method works in either case, some constructions should be
* handled at Java level to avoid locking user-visible locks.
*/
// 3 判断JVM是否支持Long类型的CAS
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
/**
* Returns whether underlying JVM supports lockless CompareAndSet
* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
*/
private static native boolean VMSupportsCS8();
static {
try {
// 4 获取value在AtomicLong中的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 5 实际变量值
private volatile long value;
/**
* Creates a new AtomicLong with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicLong(long initialValue) {
value = initialValue;
}
.......
.......
.......
.......
}
- 代码(1)通过
Unsafe.getUnsafe()
方法获取到Unsafe类的实例 为何能通过Unsafe.getUnsafe()方法获取到Unsafe类的实例?其实这是因为AtomicLong类也是在rt.jar包下面的,AtomicLong类就是通过BootStarp类加载器进行加载的。 - 代码(5)中的value被声明为volatile的,这是为了在多线程下保证内存可见性,value是具体存放计数的变量。
- 代码(2)(4)获取value变量在AtomicLong类中的偏移量。
主要方法
incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement
【JDK8 】
代码语言:javascript复制//(6)调用 Insafe方法,原子性设置 value值为原始值 1,返回值为递增后的值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) 1L;
}
//(7)调用 unsafe方法,原子性设置va1ue值为原始值-1,返回值为递减之后的值
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
//(8)调用 unsafe方法,原子性设置va1ue值为原始值 1,返回值为原始值
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//(9)调用 unsafe方法,原子性设置va1ue值为原始值-1,返回值为原始值
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
我们可以发现这几个方法内部都是通过调用Unsafe的getAndAddLong方法来实现操作,这个函数是个原子性操作。
第一个参数是AtomicLong实例的引用, 第二个参数是value变量在AtomicLong中的偏移值, 第三个参数是要设置的第二个变量的值 。
getAndIncrement方法在JDK 7中的实现逻辑为
代码语言:javascript复制public final long getAndIncrement(){
while(true){
long current=get();
long next= current 1;
if (compareAndSet(current, next))
return current
}
}
在如上代码中,每个线程是先拿到变量的当前值(由于value是volatile变量,所以这里拿到的是最新的值),然后在工作内存中对其进行增1操作,而后使用CAS修改变量的值。如果设置失败,则循环继续尝试,直到设置成功。
而JDK 8中的逻辑为
代码语言:javascript复制unsafe.getAndAddLong(this, valueOffset, -1L);
代码语言:javascript复制public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 var4));
return var6;
}
可以看到,JDK 7的AtomicLong中的循环逻辑已经被JDK 8中的原子操作类UNsafe内置了,之所以内置应该是考虑到这个函数在其他地方也会用到,而内置可以提高复用性。
boolean compareAndSet(long expect, long update)
代码语言:javascript复制 /**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
我们可以看到内部还是调用了unsafe.compareAndSwapLong
方法。如果原子变量中的value值等于expect,则使用update值更新该值并返回true,否则返回false。
小Demo
线程使用AtomicLong统计0的个数的例子
代码语言:javascript复制import java.util.concurrent.atomic.AtomicLong;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2021/11/30 22:52
* @mark: show me the code , change the world
*/
public class AtomicLongTest {
//(10)创建Long型原子计数器
private static AtomicLong atomicLong = new AtomicLong();
//(11)创建数据源
private static Integer[] arrayOne = new Integer[]{0, 1, 2, 3, 0, 5, 6, 0, 56, 0};
private static Integer[] arrayTwo = new Integer[]{10, 1, 2, 3, 0, 5, 6, 0, 56, 0};
public static void main(String[] args) throws InterruptedException {
//(12)线程one统计数组arrayOne中0的个数
Thread threadOne = new Thread(() -> {
int size = arrayOne.length;
for (int i = 0; i < size; i) {
if (arrayOne[i].intValue() == 0) {
atomicLong.incrementAndGet();
}
}
});
//(13)线程two统计数组arrayTwo中0的个数
Thread threadTwo = new Thread(() -> {
int size = arrayTwo.length;
for (int i = 0; i < size; i) {
if (arrayTwo[i].intValue() == 0) {
atomicLong.incrementAndGet();
}
}
});
//(14)启动子线程
threadOne.start();
threadTwo.start();
//(15)等待线程执行完毕
threadOne.join();
threadTwo.join();
System.out.println("count 0:" atomicLong.get());
}
}
两个线程各自统计自己所持数据中0的个数,每当找到一个0就会调用AtomicLong的原子性递增方法
小结
在没有原子类的情况下,实现计数器需要使用一定的同步措施,比如使用synchronized关键字等,但是这些都是阻塞算法,对性能有一定损耗,而这里我们介绍的这些原子操作类都使用CAS非阻塞算法,性能更好。
但是在高并发情况下AtomicLong还会存在性能问题。JDK 8提供了一个在高并发下性能更好的LongAdder类,且听下篇分解