JUC中原子操作类原理分析

微信公众号: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;
    }

其中的getLongVolatilecompareAndSwapLong方法都是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,一个在互联网行业摸鱼写代码的打工人,卑微求个【关注】和【在看】,你们的支持是我创作的最大动力,我们下期见~

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值