Atomic原子类和Unsafe魔法类 详解

1. Atomic原子类


        Atomic原子类是JUC包下的线程安全的类,其底层是通过CAS来保证,而CAS操作利用的是处理器提供的cmpxchg指令实现的。
        

1.1 Atomic原子类的作用

先看一段代码

代码一:使用synchronized保证线程安全

// 使用synchronized保证线程安全
public class AtomicTest {

    private static  volatile  int total = 0;
    private static  Object o = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        //10个线程
        for (int i = 1; i <= 10 ; i++) {
            new Thread(() ->{
                synchronized (o) {
                    for (int j = 0; j < 150000; j++) {
                        total++;
                    }
                    countDownLatch.countDown();
                }
            },"线程"+i).start();
        }
        countDownLatch.await();
        System.out.println(total);
    }
}

代码二:使用AtomicInteger 保证线程安全

// 使用Atomic原子类保证线程安全!
public class AtomicTest {

    private static AtomicInteger total = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        //10个线程
        for (int i = 1; i <= 10 ; i++) {
            new Thread(() ->{
                    for (int j = 0; j < 150000; j++) {
                        total.addAndGet(1);
                    }
                    countDownLatch.countDown();
            },"线程"+i).start();
        }
        countDownLatch.await();
        System.out.println(total);
    }
}

        以上两段代码可以看到,使用原子类AtomicInteger同样可以保证多线程total++的线程安全!那么synchronizedAtomic两种方法有什么区别呢?

先上结论:Atomic底层是基于无锁unsafe-CAS算法,保证线程安全的同时,效率比synchronized的效率要高!因为synchronized会存在锁竞争,直到升级到重量级锁。

Atomic包里的类基本都是使用Unsafe实现的包装类。
AtomicInteger为例:

   AtomicInteger atomicInteger = new AtomicInteger(0);
   // 原始值 + 10
   atomicInteger.addAndGet(10);
   public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        
		// 通过cas循环重试,来保证线程安全
		// var1:当前对象
		// var2:偏移量(用于属性寻址)
		// var5:期望值
		// var5 + var4:修改值
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

注意:Atomic修饰数组时:是把原数组复制到AtomicIntegerArray中,操作的是AtomicIntegerArray!操作完毕后 变的只是AtomicIntegerArray,原数组的值不变


public class AtomicTest {

    public static void main(String[] args) throws InterruptedException {
        int[] a = new int[]{1,2,3};
        atomicUpdateArray(a);
        System.out.println("原数组array下标0的数:"+a[0]);
    }
    
    // Atomic数组的用法
    public static  void atomicUpdateArray(int[] a) {
        AtomicIntegerArray array = new AtomicIntegerArray(a);
        //原子修改数组下标为0的值
        array.getAndSet(0, 3);
        System.out.println("AtomicArray下标0位置的数:" + array.get(0));
    }
}

结果
在这里插入图片描述

        
        

在Atomic包里一共有12个类,四种原子更新方式,分别是

  • 原子更新基本类型
  • 原子更新数组
  • 原子更新引用
  • 原子更新字段

1.2 原子更新基本类型类

用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。

AtomicInteger的常用方法如下:

  • int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
  • boolean compareAndSet(int expect, int update) :如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
  • int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
  • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
  • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

        Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有charfloatdouble等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong

        通过AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新double也可以用类似的思路来实现。

        

1.3 原子更新数组类

通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下

  • int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

        

1.4 原子更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

        

1.5 原子更新字段类

如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题

原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。

        

        

2. Unsafe魔法类

        Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。

        但由于Unsafe类使Java语言拥有了 类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。 在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语 言变得不再“安全”,因此对Unsafe的使用一定要慎重

        Unsafe还可以申请堆外内存,但是堆外内存不受GC管理,内存用完后,一定要手动释放,因为GC不会帮你释放!通过allocateMemory(size)和freeMemory(size)进行申请和释放!

        

2.1 如何获取Unsafe实例

  1. Unsafe类为一单例实现,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常。从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。
java -Xbootclasspath/a:${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径 
  1. 通过反射获取单例对象theUnsafe
public class Unsafe {
    // 单例对象
    private static final Unsafe theUnsafe;
    private Unsafe() {
    }
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
        if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
}

        

2.2 Unsafe的典型应用

  • ①:堆外内存操作。DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、 使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。
  • ②:ReentrantLockAtomic等API通过CAS修改state等等,底层用的也是Unsafe。
  • ③:线程调度。如LockSupport.park()LockSupport.unpark()实现线程的阻塞和唤醒。而 LockSupportpark、unpark方法实际是调用Unsafe的park、unpark方式来实现。
  • ④:内存屏障,通过UnsafeloadFence方法加入一个内存屏障,目的是避免指令重排
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值