zoukankan      html  css  js  c++  java
  • JUC原子类

    JUC原子类

    CAS

    实现线程安全的方法:

    • 互斥同步:synchronized,Lock.
    • 非阻塞同步:CAS,Atomic.
    • 无同步方法:栈封闭,ThreadLocal,可重入代码

    CAS介绍

    • CAS(Compare-And-Swap):对比并交换,是一个原子操作.
    • 先比较旧值是否发生变化,若没有,则交换成新值;否则不进行交换.
    • CAS是原子的,多线程使用CAS更新数据时,可以不使用锁.

    示例:

    public AtomicInteger i = new AtomicInteger(0);
    
    public int add(){
        return i.addAndGet(1);
    }
    

    存在的问题:

    • ABA问题:CAS只检查值是否发生变化,若一个值由A变为B后又变回为A,则认为没有变化而正常进行操作.
    • 循环时间长开销大:自旋CAS若长时间不成功,则会给CPU带来很大的执行开销.
    • 只能保证一个共享变量的原子操作:当对一个共享变量进行操作时,可以使用CAS保证原子操作.但是要需要对多个共享变量进行操作,则无法保证多个变量的原子性.

    解决:

    • ABA问题:使用版本号,在变量上加入版本号,每次变量变更后更新对应的版本号,若版本号一致才认为没有改变.(JUC提供AtomicStampedReference,其先检查当前引用是否等于预期引用,若等于才设置为新值)
    • 自旋问题:使用处理器提供的pause指令==>延迟流水线执行指令,避免循环时因内存顺序冲突引起的流水线被清空.
    • 多变量问题:使用AtomicReference类将多个变量放在一个对象中进行CAS操作.

    UnSafe类

    提供的功能:

    • 内存操作
    • CAS
    • Class相关
    • 对象操作
    • 线程调度
    • 系统信息获取
    • 内存屏障
    • 数组操作

    CAS原理:使用自旋调用UnSafe中CAS更新,若失败则重试.


    AtomicInteger

    常用API

    public final int get(); // 获取值
    public final int getAndSet(int newValue); // 获取当前值,并设为新值
    public final int getAndIncrement(); // 获取旧值,并自增
    public final int getAndDecrement(); // 获取旧值,并自增
    public final int getAndAdd(int delta); // 获取旧值,并加指定值
    void lazySet(int newValue); // 设为指定值,但非即时
    

    原理:使用volatile和CAS保证原子操作.

    • volatile:保证可见性,多线程并发时,一个线程修改,其他线程可见.
    • CAS:保证原子性.

    原子类小结

    原子更新基本类型

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

    原子更新数组

    • AtomicIntegerArray:原子更新整型数组.
    • AtomicLongArray:原子更新长整型数组.
    • AtomicReferenceArray:原子更新引用类型数组.
      • get(int index):获取指定位置上的元素.
      • compareAndSet(int i,E except,E update):若当前值等于预期值,则原子方式设置为新值.

    原子更新引用类型

    • AtomicReference:原子更新引用类型.
    • AtomicStampedReference:原子更新引用类型,使用Pair存储元素值和版本号.
    • AtomicMarkableReference:原子更新带有标记的引用类型.

    原子更新字段类

    • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器.
    • AtomicLongFieldUpdater:原子更新长整型的字段的更新器.
    • AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器.

    示例:

    // 1. 由AtomicIntegerFieldUpdater的newUpdater获取更新器(传入对应的类和待更新的字段名)
    // 2. 使用getAndAdd方法,传入待更新的对象实例和更新值
    // 注: 更新的字段类型要和更新器一致,否则抛出异常
    //      字段必须是volatile,保证线程间的可见性
    //      字段的修饰符要和操作对象的字段类型一致
    //      只能操作实例对象的字段,不能操作static字段
    //      不能修改final字段,不可对不可变字段更新
    //      Integer/Long包装类要使用AtomicReferenceUpdater进行修改
    public class Test{
        public void test(){
            newClass nc = new newClass();
            AtomicIntegerFieldUpdater<newClass> updater1 = AtomicIntegerFieldUpdater.newUpdater(newClass.class, "publicVal");
            int res1 = updater1.getAndAdd(nc, 2);
            System.out.println(res1);
            System.out.println(nc.publicVal);
    
            // 更新器类型与更新字段类型不一致,抛出异常
            AtomicIntegerFieldUpdater<newClass> updater2 = AtomicIntegerFieldUpdater.newUpdater(newClass.class, "integerVal");
            int res2 = updater2.getAndAdd(nc, 1);
            System.out.println(res2);
            System.out.println(nc.integerVal);
        }
    }
    
    class newClass{
        public volatile int publicVal = 1;
        protected volatile int protectedVal = 2;
        private volatile int privateVal = 3;
        public volatile static int staticVal = 4;
        public volatile Integer integerVal = 5;
        public volatile Long longVal = 6l;
    }
    

    AtomicStampedReference原理

    解决ABA问题

    静态私有类Pair包含两个域:

    • reference:维护对象引用.
    • stamp:标志版本.

    更新流程:

    • 若元素值和版本号没有改变,且更新值不变,返回true.
    • 若元素值和版本号未变,而更新值不同,则构造新的Pair对象并进行CAS更新Pair.

    小结:

    • 使用版本号控制.
    • 不重复使用Pair的引用,每次新建Pair作为CAS对象.

    示例:

    private static AtomicStampedReference stamp = new AtomicStampedReference(0,0); // 初始的reference和stamp
    
        @Test
        public void test() throws InterruptedException {
            Thread t1 = new Thread(() -> {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean isSuccess = stamp.compareAndSet(0, 1, 0, 1);
                System.out.println(isSuccess);
            });
    
            // 线程2的stamp不匹配,无法设置成功
            Thread t2 = new Thread(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean isSuccess = stamp.compareAndSet(0, 2, 1, 2); // 若设为(0,2,0,2)则有可能更新成功
                System.out.println(isSuccess);
            });
    
            t1.start();
            t2.start();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("reference is: " + stamp.getReference() + " stamp: "+stamp.getStamp());
        }
    

    参考:

  • 相关阅读:
    C语言 realloc为什么要有返回值,realloc返回值具体解释/(解决随意长度字符串输入问题)。
    opencv中的vs框架中的Blob Tracking Tests的中文注释。
    Java实现 蓝桥杯VIP 算法提高 棋盘多项式
    Java实现 蓝桥杯VIP 算法提高 棋盘多项式
    Java实现 蓝桥杯VIP 算法提高 棋盘多项式
    Java实现 蓝桥杯VIP 算法提高 棋盘多项式
    Java实现 蓝桥杯VIP 算法提高 分苹果
    Java实现 蓝桥杯VIP 算法提高 分苹果
    Java实现 蓝桥杯VIP 算法提高 分苹果
    Java实现 蓝桥杯VIP 算法提高 分苹果
  • 原文地址:https://www.cnblogs.com/truestoriesavici01/p/13214017.html
Copyright © 2011-2022 走看看