微信公众号:Zhongger
我是Zhongger,一个在互联网行业摸鱼写代码的打工人!
关注我,了解更多你不知道的【Java后端】打工技巧、职场经验、生活感悟等
一、前言
java.util.concurrent
包里提供了一系列的原子性操作类,这些类都是使用CAS机制实现的,相当于使用锁实现原子性操作在性能上有了很大的提高。由于原子性操作类的原理都大致相同,所以本文主要讲解AtomicLong类的常用方法的分析。
二、原子变量操作类AtomicLong
AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现。
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).存放变量value的偏移量
private static final long valueOffset;
//(3).native方法来判断JVM是否支持Long类型的无锁CAS
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
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;
public AtomicLong(long initialValue) {
value = initialValue;
}
由(1)处代码,获取Unsafe类对象的方法,是采用Unsafe.getUnsafe();
获取到的,因为AtomicLong类同Unsafe类一样,是在rt.jar包下的,它也是是通过BootStrap类加载器进行加载的。可以在IDE中看下AutomicLong类的位置确认一下:
科普一下,这里的类加载器涉及到JVM的知识。关于类加载器加载与所加载的类的对应关系见下图
代码(2)和(4)中获取value变量在AtomicLong类中的偏移量。
代码(5)中的value变量被声明为volatile,这是为了在多线程下保证内存可见性,value是具体存放计数的变量。
二、AtomicLong中的主要方法
1、递增和递减操作
//(6).调用unsafe的getAndAddLong方法,原子性设置value值为原始值+1,返回递增后的值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
//(7).调用unsafe的getAndAddLong方法,原子性设置value值为原始值-1,返回递减后的值
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
//(8).调用unsafe的getAndAddLong方法,原子性设置value值为原始值+1,返回原始值
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//(9).调用unsafe的getAndAddLong方法,原子性设置value值为原始值-1,返回原始值
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
上述代码内部都是通过调用Unsafe的getAndAddLong
方法来实现操作,getAndAddLong
方法是原子性操作,第一个参数是AtomicLong实例的引用,第二个参数为value在AtomicLong中的偏移量,第三个参数是要设置的第二个变量的值。
简单看一下getAndAddLong
方法的实现
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;
}
其中的getLongVolatile
和compareAndSwapLong
方法都是native方法,交给底层语言去实现。
2、boolean compareAndSet(long expect, long update)方法
看一下另外一个比较常用的compareAndSet
方法:
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
同样也是调用了compareAndSwapLong
方法。如果原子变量中的value值等于expect,则使用update值更新value值并返回true,否则返回false。
3、多线程使用AtomicLong统计0的个数
package AtomicLongLearn;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by Zhong Mingyi on 2020/11/12.
*/
public class AtomicLongTest {
//(10)创建Long型原子计数器
private static AtomicLong atomicLong = new AtomicLong();
//(11)数据源
private static Integer[] arr1 = new Integer[]{0,1,2,3,0,5,6,0,56,0};
private static Integer[] arr2 = new Integer[]{10,1,2,3,0,5,6,0,56,0};
public static void main(String[] args) throws InterruptedException {
//(12)统计arr1中0的个数
Thread thread1 = new Thread(() -> {
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] == 0) {
atomicLong.incrementAndGet();
}
}
});
//(13)统计arr2中0的个数
Thread thread2 = new Thread(() -> {
for (int i = 0; i < arr2.length; i++) {
if (arr2[i] == 0) {
atomicLong.incrementAndGet();
}
}
});
//(14)线程执行
thread1.start();
thread2.start();
//(15)等待线程执行完毕
thread1.join();
thread2.join();
System.out.println("两个数组中0的个数为:"+atomicLong.get());
}
}
如上代码中的两个线程各自统计自所持数据中0的个数,每当找到一个0就会调用AtomicLong的原子性递增方法。
如果没有原子操作类,多线程下实现计数器需要使用比如synchronized的同步措施,会对性能有一定损耗;但原子操作类都使用基于CAS的非阻塞算法,因此会获取较好的性能。
但是在高并发情况下,AtomicLong还是会存在性能问题,JDK8提供了一个在高并发下性能更好的原子类,我们下一期再来揭晓~
三、总结
本期学习了java.util.concurrent
包里提供的原子性操作类之一——AtomicLong,它其中的主要方法均使用非阻塞算法CAS实现,这比起使用锁实现原子性操作在性能上有了很大的提升;然后我们使用了AtomicLong类来实现多线程下统计0的个数的需求,并以此更好来理解AtomicLong类;但是在高并发的下,AtomicLong类仍然有一定的性能瓶颈,JDK8中有更好的类去弥补它在高并发下性能的不足。
我是Zhongger,一个在互联网行业摸鱼写代码的打工人,卑微求个【关注】和【在看】,你们的支持是我创作的最大动力,我们下期见~