JUC原子类的底层实现原理 - Unsafe中的CAS操作及JUC原子类源码解析

一直以来, JVM作为一个自动内存管理的运行时环境, 使得我们Java程序员不必像隔壁C/C++程序员那样, 操心内存的申请和释放, 摆脱了大部分因为疏忽内存管理而造成的内存泄露问题, 但是Java是否就不具备直接管理内存的能力呢?

今天我们就上手一段小程序: 多线程并发实现最常见的自增操作, 在保证线程安全的前提下.以此来实践演示一下Java中直接操作内存的技术: Unsafe.
并阐述并发编程中一个很重要的操作: CAS
需求:
1, 向线程池提交10个Task;
2, Task内容: 各自对一个共享int变量num自增1000次;
3, 期望的结果: num = 10*1000 = 10000

实现手段: 为了保证线程安全, 手段有很多, 最容易想到的就是加锁来保证操作的原子性和可见性, 例如synchronized内置互斥锁.

注: i++,++i是非原子操作, 看似只有一行Java代码, 但是其底层的汇编指令至少为3条(读值, 计算, 存值), 多线程下很容易破坏原子性导致结果错误.

新版本的synchronized(JDK5以来)有一个锁升级的过程(旧版本的就更不比说了, 直接是重量级锁), 在多线程激烈竞争下, 会经过偏向锁, 升级为轻量级锁, 最终甚至升级成为OS级别的重量级锁, 一旦到了重量锁, 多线程的阻塞和唤醒, 涉及用户态到内核态的切换, 导致该锁使用起来开销大, 性能低.(涉及Linux内存系统调用内容, 不懂的请自行了解).
而今天介绍的Unsafe通过封装了OS的cas支持, 使我们在并发程序中实现无锁操作, 屏蔽掉重量级锁的弊端.

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Instruction: 通过Unsafe实现无锁自增并测试结果.
 * Author:@author MaLi
 */
public class T02_UnsafeUsage {
   
    //通过unsafe在多线程中修改其属性值, 最后打印最终结果
    static class OptimisticLocking {
        // 待自增的整数共享变量
        private volatile int num = 0; // 此处要保证可见性, 所以加上volatile
        // 主角: 使用Unsafe直接操作内存
        private static Unsafe unsafe;
        // 内存的地址: 这里使用相对实例对象的偏移量
        private static long valueOffset;

        static {
            //初始化
            try {
                // 在字节码中获取属性
                Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                theUnsafe.setAccessible(true);
                // 关键1: 获取Unsafe实例
                unsafe = (Unsafe) theUnsafe.get(null);
                // 关键2: 获取要操作属性的address - 偏移量
                valueOffset = unsafe.objectFieldOffset(OptimisticLocking.class.getDeclaredField("num"));
                //如果是静态变量 unsafe.staticFieldOffset(静态变量);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        //使用Unsafe的cas操作, 修改num的值
        public final boolean unSafeCompareAndSet(int oleValue, int newValue) {
            //关键操作: 使用Unsafe的cas
            return unsafe.compareAndSwapInt(this, valueOffset, oleValue, newValue);

        }

        //自旋方式自增
        public void selfPlus() {
            int oldValue = num;
            do {
                //获取当前最新num值
                oldValue = num;
            } while (!unSafeCompareAndSet(oldValue, oldValue + 1));
        }
    }

    // 测试用例: 提交10个并行Task, 每个Task使用自造的selfPlus实现自加1000次, 期望的结果是1万
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(10);
        OptimisticLocking optimisticLocking = new OptimisticLocking();
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            service.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    optimisticLocking.selfPlus(); //调用自造的Api实现无锁自增
                }
                latch.countDown(); //10个线程总共倒数10个数
            });
        }
        try {
            latch.await();  //让MainThread在此等待所有Task执行完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(optimisticLocking.num);  //期望的结果: 10000
        service.shutdownNow();                      //暴力关闭线程池
    }
}

总结: 本文代码主要为了实现原子自增,来了解JUC中原子类的实现原理
关键点是通过对volatile修饰的变量实现CAS+自旋操作.
自旋的目的: 如果没有通过CAS设置value成功, 就获取当前value值, 重新CAS设置.

另外, 其实在java并发包中提供了很多原子类, 作用是在并发编程中, 实现线程安全的原子操作 - 自增,自减, CAS设置值等. 这些内容属于原子类的使用问题, 用起来很简单, 关键是其底层原理都使用了我们今天介绍的Unsafe的CAS操作. 下面列举部分源码作为说明

Step1: AotmicInteger的自增1
–> java.util.concurrent.atomic.AtomicInteger
关键点1: 调用Unsafe的函数

	
	// 该函数的作用类似: i++, 即先获取值, 再做运算
	public final int getAndDecrement() {
        return U.getAndAddInt(this, VALUE, -1);
    }

Step2: Unsafe的getAndAddInt函数调用
–> jdk.internal.misc.Unsafe

	// 
	/** 
	* Object o: 要操作的变量所属实例
	* long offset: 待操作的变量在所属对象中的偏移量, 借此+ Object o计算出该变量在内存中的位置
	* int delta: 
	*/
	@HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v; // 创建一个变量保存当前变量的值
        do {
            v = getIntVolatile(o, offset); // 获取变量最新值(可能是其它线程改变过的)
        } while (!weakCompareAndSetInt(o, offset, v, v + delta)); // 如果没有修改成功, 继续, 直到自增完成
        return v; // 返回修改前的值, 也就是i++的i
    }

–> jdk.internal.misc.Unsafe

	@HotSpotIntrinsicCandidate
	/**
    * Object o : 要修改的变量所在对象实例
    * long offset: 变量相对其所属对象的偏移量
    * int expected: 期望内存中的值
    * int x: 修改为目标值
    */
    public final boolean weakCompareAndSetInt(Object o, long offset,
                                              int expected,
                                              int x) {
        return compareAndSetInt(o, offset, expected, x);
    }

–> jdk.internal.misc.Unsafe

    @HotSpotIntrinsicCandidate
    // 这是一个native方法, 也就是用C++代码对OS中CAS的封装
    /**
    * Object o : 要修改的变量所在对象实例
    * long offset: 变量相对其所属对象的偏移量
    * int expected: 期望内存中的值
    * int x: 修改为目标值
    */
    public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);

除去Unsafe这些关键的函数, 一定不要忘记还有一个关键点:
关键点2: AtomicInteger中一行代码

private volatile int value;

就是其中这个value值必须为volatile的, 加volatile关键字的原因, 多线程操作共享的变量value, CAS只是保证计算, 但前提是各个线程在各自CPU缓存里要看见彼此计算之后对value的修改, 也就是保证线程之间的可见性, 所以必须加上它.
至此,通过自己手写了一个自增原子操作, 另外解析了一个原子类自增源码. 原子类的使用, 非常简单就不多罗列了.
后续想到继续添加.

关于本文, 有疑问欢迎通过留言交流.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

能力工场小马哥

如果对您有帮助, 请打赏支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值