一、前言
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类的位置确认一下:
由(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中有更好的类去弥补它在高并发下性能的不足。