精华内容
下载资源
问答
  • 根据操作的数据类型,原子类可以分为以下几类。基本类型AtomicInteger:整型原子类AtomicLong:长整型原子类AtomicBoolean:布尔型原子类AtomicInteger的常用方法public AtomicInteger常见方法的使用@Tes...

    14c10b471faf57b98dc8103f57ff89e7.png
    本文基于JDK1.8

    Atomic原子类

    原子类是具有原子操作特征的类。

    原子类存在于java.util.concurrent.atmic包下。

    根据操作的数据类型,原子类可以分为以下几类。

    基本类型

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

    AtomicInteger的常用方法

    public final int get() //获取当前的值
    public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    public final int getAndIncrement()//获取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //加上给定的值,并返回之前的值
    public final int addAndGet(int delta) //加上给定的值,并返回最终结果
    boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
    public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值 

    AtomicInteger常见方法的使用

    @Test
    public void AtomicIntegerT() {
    
        AtomicInteger c = new AtomicInteger();
    
        c.set(10);
        System.out.println("初始设置的值 ==>" + c.get());
    
        int andAdd = c.getAndAdd(10);
        System.out.println("为原先的值加上10,并返回原先的值,原先的值是 ==> " + andAdd + "加上之后的值是 ==> " + c.get());
    
        int finalVal = c.addAndGet(5);
        System.out.println("加上5, 之后的值是 ==> " + finalVal);
    
        int i = c.incrementAndGet();
        System.out.println("++1,之后的值为 ==> " + i);
        
        int result = c.updateAndGet(e -> e + 3);
        System.out.println("可以使用函数式更新 + 3 计算后的结果为 ==> "+ result);
    
        int res = c.accumulateAndGet(10, (x, y) -> x + y);
        System.out.println("使用指定函数计算后的结果为 ==>" + res);
    }
    
    初始设置的值 ==>10
    为原先的值加上10,并返回原先的值,原先的值是 ==> 10 
    加上之后的值是 ==> 20
    加上5, 之后的值是 ==> 25
    ++1,之后的值为 ==> 26
    可以使用函数式更新 + 3 计算后的结果为 ==> 29
    使用指定函数计算后的结果为 ==>39 

    AtomicInteger保证原子性

    我们知道,volatile可以保证可见性和有序性,但是不能保证原子性,因此,以下的代码在并发环境下的结果会不正确:最终的结果可能会小于10000。

    public class AtomicTest {
    
        static CountDownLatch c = new CountDownLatch(10);
        public volatile int inc = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            final AtomicTest test = new AtomicTest();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();
                    }
                    c.countDown();
                }).start();
            }
            c.await();
            System.out.println(test.inc);
    
        }
        //不是原子操作, 先读取inc的值, inc + 1, 写回内存
        public void increase() {
            inc++;
        }
    } 

    想要解决最终结果不是10000的办法有两个:

    • 使用synchronized关键字,修饰increase方法,锁可以保证该方法某一时刻只能有一个线程执行,保证了原子性。
     public synchronized void increase() {
            inc++;
        } 

    使用Atomic原子类,比如这里的AtomicInteger

    public class AtomicTest {
    
        static CountDownLatch c = new CountDownLatch(10);
    
        // 使用整型原子类 保证原子性
        public AtomicInteger inc = new AtomicInteger();
    
        public static void main(String[] args) throws InterruptedException {
    
            final AtomicTest test = new AtomicTest();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();
                    }
                    c.countDown();
                }).start();
            }
            c.await();
            System.out.println(test.getCount());
        }
    
        // 获取当前的值,并自增
        public void increase() {
            inc.getAndIncrement();
        }
    
        // 获取当前的值
        public int getCount() {
            return inc.get();
        }
    } 

    getAndIncrement()方法的实现

    getAndIncrement方法是如何确保原子操作的呢?

     private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                //objectFieldOffset本地方法,用来拿到“原来的值”的内存地址。
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
        //value在内存中可见,JVM可以保证任何时刻任何线程总能拿到该变量的最新值
        private volatile int value;   
    
    
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        } 

    openjdk1.8Unsafe类的源码:Unsafe.java

     /**
         * Atomically adds the given value to the current value of a field
         * or array element within the given object <code>o</code>
         * at the given <code>offset</code>.
         *
         * @param o object/array to update the field/element in
         * @param offset field/element offset
         * @param delta the value to add
         * @return the previous value
         * @since 1.8
         */
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        } 

    Java的源码改动是有的,《Java并发编程的艺术》的内容也在此摘录一下,相对来说更好理解一些:

     public final int getAddIncrement() {
            for ( ; ; ) {
                //先取得存储的值
                int current = get();
                //加1操作
                int next = current + 1;
                // CAS保证原子更新操作,如果输入的数值等于预期值,将值设置为输入的值
                if (compareAndSet(current, next)) {
                    return current;
                }
            }
        }
    
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 

    数组类型

    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray :引用类型数组原子类

    AtomicIntegerArray的常用方法

    @Test
    public void AtomicIntegerArrayT() {
    
        int[] nums = {1, 2, 3, 4, 5};
        AtomicIntegerArray c = new AtomicIntegerArray(nums);
    
        for (int i = 0; i < nums.length; i++) {
            System.out.print(c.get(i) + " ");
        }
        System.out.println();
    
        int finalVal = c.addAndGet(0, 10);
        System.out.println("索引为 0 的值 加上 10  ==> " + finalVal);
    
        int i = c.incrementAndGet(0);
        System.out.println("索引为 0 的值 ++1,之后的值为 ==> " + i);
    
        int result = c.updateAndGet(0, e -> e + 3);
        System.out.println("可以使用函数式更新索引为0 的位置 + 3 计算后的结果为 ==> " + result);
    
        int res = c.accumulateAndGet(0, 10, (x, y) -> x * y);
        System.out.println("使用指定函数计算后的结果为 ==> " + res);
    } 

    引用类型

    基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。

    • AtomicReference:引用类型原子类
    • AtomicMarkableReference:原子更新带有标记的引用类型,无法解决ABA问题,该类的标记更多用于表示引用值是否已逻辑删除
    • AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

    AtomicReference常见方法的使用

    @Test
    public void AtomicReferenceT(){
    
        AtomicReference<Person> ar = new AtomicReference<>();
        Person p = new Person(18,"summer");
    
        ar.set(p);
    
        Person pp = new Person(50,"dan");
        ar.compareAndSet(p, pp);// except = p  update = pp
    
        System.out.println(ar.get().getName());
        System.out.println(ar.get().getAge());
    
    }
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    class Person{
    
        int age;
        String name;
    }
    //dan
    //50 

    对象的属性修改类型

    如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。

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

    要想原子地更新对象的属性需要两步。

    1. 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
    2. 更新的对象属性必须使用 public volatile 修饰符。

    AtomicIntegerFieldUpdater常用方法的使用

    @Test
    public void AtomicIntegerFieldUpdateTest(){
        AtomicIntegerFieldUpdater<Person> a =
            AtomicIntegerFieldUpdater.newUpdater(Person.class,"age");
        Person p = new Person(18,"summer");
        System.out.println(a.getAndIncrement(p)); //18
        System.out.println(a.get(p)); //19
    }
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    class Person{
    
        public volatile int age;
        private String name;
    } 

    Java8新增的原子操作类

    • LongAdder

    由于AtomicLong通过CAS提供非阻塞的原子性操作,性能已经很好,在高并发下大量线程竞争更新同一个原子量,但只有一个线程能够更新成功,这就造成大量的CPU资源浪费。

    LongAdder 通过让多个线程去竞争多个Cell资源,来解决,再很高的并发情况下,线程操作的是Cell数组,并不是base,在cell元素不足时进行2倍扩容,在高并发下性能高于AtomicLong

    CAS的ABA问题的产生

    假设两个线程访问同一变量x。

    1. 第一个线程获取到了变量x的值A,然后执行自己的逻辑。
    2. 这段时间内,第二个线程也取到了变量x的值A,然后将变量x的值改为B,然后执行自己的逻辑,最后又把变量x的值变为A【还原】。
    3. 在这之后,第一个线程终于进行了变量x的操作,但此时变量x的值还是A,以为x的值没有变化,所以compareAndSet还是会成功执行。

    先来看一个值变量产生的ABA问题,理解一下ABA问题产生的流程:

    @SneakyThrows
    @Test
    public void test1() {
        AtomicInteger atomicInteger = new AtomicInteger(10);
    
        CountDownLatch countDownLatch = new CountDownLatch(2);
    
        new Thread(() -> {
            atomicInteger.compareAndSet(10, 11);
            atomicInteger.compareAndSet(11,10);
            System.out.println(Thread.currentThread().getName() + ":10->11->10");
            countDownLatch.countDown();
        }).start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                boolean isSuccess = atomicInteger.compareAndSet(10,12);
                System.out.println("设置是否成功:" + isSuccess + ",设置的新值:" + atomicInteger.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }).start();
    
        countDownLatch.await();
    }
    //输出:线程2并没有发现初始值已经被修改
    //Thread-0:10->11->10
    //设置是否成功true,设置的新值12 

    ABA问题存在,但可能对值变量并不会造成结果上的影响,但是考虑一种特殊的情况:

    https://zhuanlan.zhihu.com/p/237611535

    13ccbb476a8e3d9c1a2d2c1716f0f0a0.png
    1. 线程1和线程2并发访问ConcurrentStack。
    2. 线程1执行出栈【预期结果是弹出B,A成为栈顶】,但在读取栈顶B之后,被线程2抢占。
    3. 线程2记录栈顶B,依次弹出B和A,再依次将C,D,B入栈,且保证B就是原栈顶记录的B。
    4. 之后轮到线程1,发现栈顶确实是期望的B,遂弹出B,但此时栈顶已经是D,就出现了错误。

    BAB的问题如何解决

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

    @SneakyThrows
    @Test
    public void test2() {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(10,1);
    
        CountDownLatch countDownLatch = new CountDownLatch(2);
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(10, 11, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第二次版本:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(11, 10, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第三次版本:" + atomicStampedReference.getStamp());
            countDownLatch.countDown();
        }).start();
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
                boolean isSuccess = atomicStampedReference.compareAndSet(10,12, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 当前版本:" + atomicStampedReference.getStamp() + " 当前值:" + atomicStampedReference.getReference());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    
        countDownLatch.await();
    }
    //输出
    //输出
    Thread-0 第一次版本1
    Thread-0 第二次版本2
    Thread-0 第三次版本3
    Thread-1 第一次版本3
    Thread-1 修改是否成功true 当前版本4 当前值12

    而AtomicMarkableReference 通过标志位,标志位只有true和false,每次更新标志位的话,在第三次的时候,又会变得跟第一次一样,并不能解决ABA问题。

    @SneakyThrows
    @Test
    public void test3() {
        AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(10, false);
    
        CountDownLatch countDownLatch = new CountDownLatch(2);
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次标记:" + markableReference.isMarked());
            markableReference.compareAndSet(10, 11, markableReference.isMarked(), true);
            System.out.println(Thread.currentThread().getName() + " 第二次标记:" + markableReference.isMarked());
            markableReference.compareAndSet(11, 10, markableReference.isMarked(), false);
            System.out.println(Thread.currentThread().getName() + " 第三次标记:" + markableReference.isMarked());
            countDownLatch.countDown();
        }).start();
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次标记:" + markableReference.isMarked());
            try {
                TimeUnit.SECONDS.sleep(2);
                boolean isSuccess = markableReference.compareAndSet(10,12, false, true);
                System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 当前标记:" + markableReference.isMarked() + " 当前值:" + markableReference.getReference());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    
        countDownLatch.await();
    }
    //输出
    Thread-0 第一次标记false
    Thread-0 第二次标记true
    Thread-0 第三次标记false
    Thread-1 第一次标记false
    Thread-1 修改是否成功true 当前标记true 当前值12

    如果觉得本文对你有帮助,可以点赞支持一下,一起学习进步

    最后

    Java程序员必备的一个Java核心知识点和程序员交流集中聚集地

    程序员必备 Java 核心知识点整理zhuanlan.zhihu.com
    823d7bd644388e15da3493f0320b3471.png
    Java程序员交流圈 - 知乎www.zhihu.com
    11ff7f8a06d3eeca4c7e0e50b2ec818f.png

    09ece7b0f75720ad23acf60316baba3c.png
    作者:一个帅气的人
    文章链接:https://segmentfault.com/a/1190000025131009
    来源: SegmentFault
    展开全文
  • 根据操作的数据类型,原子类可以分为以下几类。基本类型AtomicInteger:整型原子类AtomicLong:长整型原子类AtomicBoolean:布尔型原子类AtomicInteger的常用方法public final int get() //获取当前的值...

    235108db65ec3f5b14992756cd84d6c3.png
    本文基于JDK1.8

    Atomic原子类

    原子类是具有原子操作特征的类。

    原子类存在于java.util.concurrent.atmic包下。

    根据操作的数据类型,原子类可以分为以下几类。

    基本类型

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

    AtomicInteger的常用方法

    public final int get() //获取当前的值
    public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    public final int getAndIncrement()//获取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //加上给定的值,并返回之前的值
    public final int addAndGet(int delta) //加上给定的值,并返回最终结果
    boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
    public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
    

    AtomicInteger常见方法的使用

    @Test
    public void AtomicIntegerT() {
        AtomicInteger c = new AtomicInteger();
        c.set(10);
        System.out.println("初始设置的值 ==>" + c.get());
        int andAdd = c.getAndAdd(10);
        System.out.println("为原先的值加上10,并返回原先的值,原先的值是 ==> " + andAdd + "加上之后的值是 ==> " + c.get());
        int finalVal = c.addAndGet(5);
        System.out.println("加上5, 之后的值是 ==> " + finalVal);
        int i = c.incrementAndGet();
        System.out.println("++1,之后的值为 ==> " + i);
            int result = c.updateAndGet(e -> e + 3);
        System.out.println("可以使用函数式更新 + 3 计算后的结果为 ==> "+ result);
        int res = c.accumulateAndGet(10, (x, y) -> x + y);
        System.out.println("使用指定函数计算后的结果为 ==>" + res);
    }初始设置的值 ==>10
    为原先的值加上10,并返回原先的值,原先的值是 ==> 10 
    加上之后的值是 ==> 20
    加上5, 之后的值是 ==> 25
    ++1,之后的值为 ==> 26
    可以使用函数式更新 + 3 计算后的结果为 ==> 29
    使用指定函数计算后的结果为 ==>39
    

    AtomicInteger保证原子性

    我们知道,volatile可以保证可见性和有序性,但是不能保证原子性,因此,以下的代码在并发环境下的结果会不正确:最终的结果可能会小于10000。

    public class AtomicTest {
        static CountDownLatch c = new CountDownLatch(10);
        public volatile int inc = 0;
        public static void main(String[] args) throws InterruptedException {
            final AtomicTest test = new AtomicTest();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();                }                c.countDown();            }).start();        }        c.await();
            System.out.println(test.inc);
        }    //不是原子操作, 先读取inc的值, inc + 1, 写回内存
        public void increase() {
            inc++;
        }
    }
    

    想要解决最终结果不是10000的办法有两个:

    • 使用synchronized关键字,修饰increase方法,锁可以保证该方法某一时刻只能有一个线程执行,保证了原子性。
    public synchronized void increase() {
            inc++;    }
    • 使用Atomic原子类,比如这里的AtomicInteger。
    public class AtomicTest {
        static CountDownLatch c = new CountDownLatch(10);
        // 使用整型原子类 保证原子性
        public AtomicInteger inc = new AtomicInteger();
        public static void main(String[] args) throws InterruptedException {
            final AtomicTest test = new AtomicTest();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();
                    }
                    c.countDown();
                }).start();
            }
            c.await();
            System.out.println(test.getCount());
        }
        // 获取当前的值,并自增
        public void increase() {
            inc.getAndIncrement();
        }
        // 获取当前的值
        public int getCount() {
            return inc.get();
        }
    }
    

    getAndIncrement()方法的实现

    getAndIncrement方法是如何确保原子操作的呢?

    private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
        static {
            try {
                //objectFieldOffset本地方法,用来拿到“原来的值”的内存地址。
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    	//value在内存中可见,JVM可以保证任何时刻任何线程总能拿到该变量的最新值
        private volatile int value;   
    	public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    

    openjdk1.8Unsafe类的源码:Unsafe.java

    /**
         * Atomically adds the given value to the current value of a field
         * or array element within the given object <code>o</code>
         * at the given <code>offset</code>.
         *
         * @param o object/array to update the field/element in
         * @param offset field/element offset
         * @param delta the value to add
         * @return the previous value
         * @since 1.8
         */
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);        } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        }

    Java的源码改动是有的,《Java并发编程的艺术》的内容也在此摘录一下,相对来说更好理解一些:

    public final int getAddIncrement() {
            for ( ; ; ) {
                //先取得存储的值
                int current = get();
                //加1操作
                int next = current + 1;
                // CAS保证原子更新操作,如果输入的数值等于预期值,将值设置为输入的值
                if (compareAndSet(current, next)) {
                    return current;
                }        }    }    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    

    数组类型

    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray :引用类型数组原子类

    AtomicIntegerArray的常用方法

    @Test
    public void AtomicIntegerArrayT() {
        int[] nums = {1, 2, 3, 4, 5};
        AtomicIntegerArray c = new AtomicIntegerArray(nums);
        for (int i = 0; i < nums.length; i++) {
            System.out.print(c.get(i) + " ");
        }    System.out.println();
        int finalVal = c.addAndGet(0, 10);
        System.out.println("索引为 0 的值 加上 10  ==> " + finalVal);
        int i = c.incrementAndGet(0);
        System.out.println("索引为 0 的值 ++1,之后的值为 ==> " + i);
        int result = c.updateAndGet(0, e -> e + 3);
        System.out.println("可以使用函数式更新索引为0 的位置 + 3 计算后的结果为 ==> " + result);
        int res = c.accumulateAndGet(0, 10, (x, y) -> x * y);
        System.out.println("使用指定函数计算后的结果为 ==> " + res);
    }

    引用类型

    基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。

    • AtomicReference:引用类型原子类
    • AtomicMarkableReference:原子更新带有标记的引用类型,无法解决ABA问题,该类的标记更多用于表示引用值是否已逻辑删除
    • AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

    AtomicReference常见方法的使用

    @Test
    public void AtomicReferenceT(){    AtomicReference<Person> ar = new AtomicReference<>();
        Person p = new Person(18,"summer");
        ar.set(p);    Person pp = new Person(50,"dan");
        ar.compareAndSet(p, pp);// except = p  update = pp
        System.out.println(ar.get().getName());    System.out.println(ar.get().getAge());}@Data@AllArgsConstructor@NoArgsConstructorclass Person{    int age;    String name;}//dan
    //50
    

    对象的属性修改类型

    如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。

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

    要想原子地更新对象的属性需要两步。

    1. 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
    2. 更新的对象属性必须使用 public volatile 修饰符。

    AtomicIntegerFieldUpdater常用方法的使用

    @Test
    public void AtomicIntegerFieldUpdateTest(){    AtomicIntegerFieldUpdater<Person> a =        AtomicIntegerFieldUpdater.newUpdater(Person.class,"age");
        Person p = new Person(18,"summer");
        System.out.println(a.getAndIncrement(p)); //18
        System.out.println(a.get(p)); //19
    }@Data@AllArgsConstructor@NoArgsConstructorclass Person{    public volatile int age;    private String name;}

    Java8新增的原子操作类

    • LongAdder

    由于AtomicLong通过CAS提供非阻塞的原子性操作,性能已经很好,在高并发下大量线程竞争更新同一个原子量,但只有一个线程能够更新成功,这就造成大量的CPU资源浪费。

    LongAdder 通过让多个线程去竞争多个Cell资源,来解决,在很高的并发情况下,线程操作的是Cell数组,并不是base,在cell元素不足时进行2倍扩容,在高并发下性能高于AtomicLong

    CAS的ABA问题的产生

    假设两个线程访问同一变量x。

    1. 第一个线程获取到了变量x的值A,然后执行自己的逻辑。
    2. 这段时间内,第二个线程也取到了变量x的值A,然后将变量x的值改为B,然后执行自己的逻辑,最后又把变量x的值变为A【还原】。
    3. 在这之后,第一个线程终于进行了变量x的操作,但此时变量x的值还是A,因为x的值没有变化,所以compareAndSet还是会成功执行。

    先来看一个值变量产生的ABA问题,理解一下ABA问题产生的流程:

    @SneakyThrows
    @Testpublic void test1() {    AtomicInteger atomicInteger = new AtomicInteger(10);
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            atomicInteger.compareAndSet(10, 11);
            atomicInteger.compareAndSet(11,10);
            System.out.println(Thread.currentThread().getName() + ":10->11->10");
            countDownLatch.countDown();    }).start();    new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                boolean isSuccess = atomicInteger.compareAndSet(10,12);
                System.out.println("设置是否成功:" + isSuccess + ",设置的新值:" + atomicInteger.get());
            } catch (InterruptedException e) {
                e.printStackTrace();        }        countDownLatch.countDown();    }).start();    countDownLatch.await();
    }//输出:线程2并没有发现初始值已经被修改
    //Thread-0:10->11->10
    //设置是否成功:true,设置的新值:12
    

    ABA问题存在,但可能对值变量并不会造成结果上的影响,但是考虑一种特殊的情况:

    6b64dacae2d1c1ce7e7281b021aae252.png
    1. 线程1和线程2并发访问ConcurrentStack。
    2. 线程1执行出栈【预期结果是弹出B,A成为栈顶】,但在读取栈顶B之后,被线程2抢占。
    3. 线程2记录栈顶B,依次弹出B和A,再依次将C,D,B入栈,且保证B就是原栈顶记录的B。
    4. 之后轮到线程1,发现栈顶确实是期望的B,虽弹出B,但此时栈顶已经是D,就出现了错误。

    BAB的问题如何解决

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

    @SneakyThrows
    @Testpublic void test2() {    AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(10,1);
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(10, 11, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第二次版本:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(11, 10, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第三次版本:" + atomicStampedReference.getStamp());
            countDownLatch.countDown();    }).start();    new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
                boolean isSuccess = atomicStampedReference.compareAndSet(10,12, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 当前版本:" + atomicStampedReference.getStamp() + " 当前值:" + atomicStampedReference.getReference());
                countDownLatch.countDown();        } catch (InterruptedException e) {
                e.printStackTrace();        }    }).start();    countDownLatch.await();
    }//输出
    //输出
    Thread-0 第一次版本:1
    Thread-0 第二次版本:2
    Thread-0 第三次版本:3
    Thread-1 第一次版本:3
    Thread-1 修改是否成功:true 当前版本:4 当前值:12
    

    而AtomicMarkableReference 通过标志位,标志位只有true和false,每次更新标志位的话,在第三次的时候,又会变得跟第一次一样,并不能解决ABA问题。

    @SneakyThrows
    @Testpublic void test3() {    AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(10, false);
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次标记:" + markableReference.isMarked());
            markableReference.compareAndSet(10, 11, markableReference.isMarked(), true);
            System.out.println(Thread.currentThread().getName() + " 第二次标记:" + markableReference.isMarked());
            markableReference.compareAndSet(11, 10, markableReference.isMarked(), false);
            System.out.println(Thread.currentThread().getName() + " 第三次标记:" + markableReference.isMarked());
            countDownLatch.countDown();    }).start();    new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次标记:" + markableReference.isMarked());
            try {
                TimeUnit.SECONDS.sleep(2);
                boolean isSuccess = markableReference.compareAndSet(10,12, false, true);
                System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 当前标记:" + markableReference.isMarked() + " 当前值:" + markableReference.getReference());
                countDownLatch.countDown();        } catch (InterruptedException e) {
                e.printStackTrace();        }    }).start();    countDownLatch.await();
    }//输出
    Thread-0 第一次标记:false
    Thread-0 第二次标记:true
    Thread-0 第三次标记:false
    Thread-1 第一次标记:false
    Thread-1 修改是否成功:true 当前标记:true 当前值:12

    如果觉得本文对你有帮助,可以点赞关注支持一下,也可以点进我主页关注我公众号,上面有更多技术干货文章以及相关资料共享,大家一起学习进步!

    展开全文
  • 7.Atomic原子类

    2021-03-02 18:09:26
    2.atomic包(JUC包)下分为几类? 3.说说AtomicInteger怎么使用? 4.说说AtomicInteger是什么实现的?原理? 5.为什么使用Atomic原子类? Q:说说什么是Atomic原子类?什么是原子性?Atomic原子类有什么作用? ...

    1.说说什么是Atomic原子类?什么是原子性?Atomic原子类有什么作用?
    2.atomic包(JUC包)下分为哪几类?
    3.说说AtomicInteger怎么使用?
    4.说说AtomicInteger是什么实现的?原理?
    5.为什么使用Atomic原子类?


    Q:说说什么是Atomic原子类?什么是原子性?Atomic原子类有什么作用?
    A:
    Atomic原子类是在java.concurrent.atomic包(简称JUC包)下。它是提供变量原子操作的类
    操作的原子性,也叫原子操作。指的是在多个线程并发运行的时候,该操作不会被其它线程所干扰,是最小的执行单位,不可分割。

    作用:不需要加锁,就可以保证变量的线程安全

    Q:atomic包(JUC包)下分为哪几类?
    A:
    1.基本类型 2.数组类型 .3.引用类型 4.修改对象属性的类型(field updater)

    基本类型中只有integer long boolean的原子操作。

    Q:说说AtomicInteger怎么使用?
    A:
    有int getAndSet(int newValue) int getAndIncrement() int incrementAndGet()
    boolean compareAndSet(int expect,int update) 等方法来实现整形的原子操作

    expect是期望的值,update是要修改的值。如果现在内存中的值等于expect的话,就把update就修改好,返回true。当内存中的值不等于expect的话修改失败,返回false

    剩下get在前面的话返回的是旧的值get在后面的话返回的是新的值

    此时不需要对该原子整形变量加锁就可以保证线程安全

    Q:说说AtomicInteger是什么实现的?原理?
    A:
    AtomicInteger底层是使用CAS(compare and swap) + volatile(加在那个value上) + native方法(是由c/c++实现的访问操作系统底层的方法

    CAS是是怎么实现的?以及相关问题?(ABA问题什么的,打一套)
    value变量加了volatile表示它对于所有线程都是可见的,所有线程都可以拿到它的最新值。
    native方法:在unsafe类中可以访问操作系统底层的c/c++代码
    用unsafe得到了该变量(原来的值)在内存的地址valueoffset (offset–开端,起始 也就是地址)

    Q:为什么使用Atomic原子类?
    A:
    只需要保证变量的线程安全时,使用它来代替synchronized加锁,来提高性能

    展开全文
  • 根据操作的数据类型,原子类可以分为以下几类。基本类型AtomicInteger:整型原子类AtomicLong:长整型原子类AtomicBoolean:布尔型原子类AtomicInteger的常用方法public final int get() //获取当前的值...

    d5e3466b5effaec1c6293d1c7204cefd.png
    本文基于JDK1.8

    Atomic原子类

    原子类是具有原子操作特征的类。

    原子类存在于java.util.concurrent.atmic包下。

    根据操作的数据类型,原子类可以分为以下几类。

    基本类型

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

    AtomicInteger的常用方法

    public final int get() //获取当前的值
    public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    public final int getAndIncrement()//获取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //加上给定的值,并返回之前的值
    public final int addAndGet(int delta) //加上给定的值,并返回最终结果
    boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
    public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值 

    AtomicInteger常见方法的使用

    @Test
    public void AtomicIntegerT() {
    
        AtomicInteger c = new AtomicInteger();
    
        c.set(10);
        System.out.println("初始设置的值 ==>" + c.get());
    
        int andAdd = c.getAndAdd(10);
        System.out.println("为原先的值加上10,并返回原先的值,原先的值是 ==> " + andAdd + "加上之后的值是 ==> " + c.get());
    
        int finalVal = c.addAndGet(5);
        System.out.println("加上5, 之后的值是 ==> " + finalVal);
    
        int i = c.incrementAndGet();
        System.out.println("++1,之后的值为 ==> " + i);
        
        int result = c.updateAndGet(e -> e + 3);
        System.out.println("可以使用函数式更新 + 3 计算后的结果为 ==> "+ result);
    
        int res = c.accumulateAndGet(10, (x, y) -> x + y);
        System.out.println("使用指定函数计算后的结果为 ==>" + res);
    }
    
    初始设置的值 ==>10
    为原先的值加上10,并返回原先的值,原先的值是 ==> 10 
    加上之后的值是 ==> 20
    加上5, 之后的值是 ==> 25
    ++1,之后的值为 ==> 26
    可以使用函数式更新 + 3 计算后的结果为 ==> 29
    使用指定函数计算后的结果为 ==>39 

    AtomicInteger保证原子性

    我们知道,volatile可以保证可见性和有序性,但是不能保证原子性,因此,以下的代码在并发环境下的结果会不正确:最终的结果可能会小于10000。

    public class AtomicTest {
    
        static CountDownLatch c = new CountDownLatch(10);
        public volatile int inc = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            final AtomicTest test = new AtomicTest();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();
                    }
                    c.countDown();
                }).start();
            }
            c.await();
            System.out.println(test.inc);
    
        }
        //不是原子操作, 先读取inc的值, inc + 1, 写回内存
        public void increase() {
            inc++;
        }
    } 

    想要解决最终结果不是10000的办法有两个:

    • 使用synchronized关键字,修饰increase方法,锁可以保证该方法某一时刻只能有一个线程执行,保证了原子性。
     public synchronized void increase() {
            inc++;
        } 

    使用Atomic原子类,比如这里的AtomicInteger

    public class AtomicTest {
    
        static CountDownLatch c = new CountDownLatch(10);
    
        // 使用整型原子类 保证原子性
        public AtomicInteger inc = new AtomicInteger();
    
        public static void main(String[] args) throws InterruptedException {
    
            final AtomicTest test = new AtomicTest();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();
                    }
                    c.countDown();
                }).start();
            }
            c.await();
            System.out.println(test.getCount());
        }
    
        // 获取当前的值,并自增
        public void increase() {
            inc.getAndIncrement();
        }
    
        // 获取当前的值
        public int getCount() {
            return inc.get();
        }
    } 

    getAndIncrement()方法的实现

    getAndIncrement方法是如何确保原子操作的呢?

     private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                //objectFieldOffset本地方法,用来拿到“原来的值”的内存地址。
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
        //value在内存中可见,JVM可以保证任何时刻任何线程总能拿到该变量的最新值
        private volatile int value;   
    
    
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        } 

    openjdk1.8Unsafe类的源码:Unsafe.java

     /**
         * Atomically adds the given value to the current value of a field
         * or array element within the given object <code>o</code>
         * at the given <code>offset</code>.
         *
         * @param o object/array to update the field/element in
         * @param offset field/element offset
         * @param delta the value to add
         * @return the previous value
         * @since 1.8
         */
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        } 

    Java的源码改动是有的,《Java并发编程的艺术》的内容也在此摘录一下,相对来说更好理解一些:

     public final int getAddIncrement() {
            for ( ; ; ) {
                //先取得存储的值
                int current = get();
                //加1操作
                int next = current + 1;
                // CAS保证原子更新操作,如果输入的数值等于预期值,将值设置为输入的值
                if (compareAndSet(current, next)) {
                    return current;
                }
            }
        }
    
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 

    数组类型

    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray :引用类型数组原子类

    AtomicIntegerArray的常用方法

    @Test
    public void AtomicIntegerArrayT() {
    
        int[] nums = {1, 2, 3, 4, 5};
        AtomicIntegerArray c = new AtomicIntegerArray(nums);
    
        for (int i = 0; i < nums.length; i++) {
            System.out.print(c.get(i) + " ");
        }
        System.out.println();
    
        int finalVal = c.addAndGet(0, 10);
        System.out.println("索引为 0 的值 加上 10  ==> " + finalVal);
    
        int i = c.incrementAndGet(0);
        System.out.println("索引为 0 的值 ++1,之后的值为 ==> " + i);
    
        int result = c.updateAndGet(0, e -> e + 3);
        System.out.println("可以使用函数式更新索引为0 的位置 + 3 计算后的结果为 ==> " + result);
    
        int res = c.accumulateAndGet(0, 10, (x, y) -> x * y);
        System.out.println("使用指定函数计算后的结果为 ==> " + res);
    } 

    引用类型

    基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。

    • AtomicReference:引用类型原子类
    • AtomicMarkableReference:原子更新带有标记的引用类型,无法解决ABA问题,该类的标记更多用于表示引用值是否已逻辑删除
    • AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

    AtomicReference常见方法的使用

    @Test
    public void AtomicReferenceT(){
    
        AtomicReference<Person> ar = new AtomicReference<>();
        Person p = new Person(18,"summer");
    
        ar.set(p);
    
        Person pp = new Person(50,"dan");
        ar.compareAndSet(p, pp);// except = p  update = pp
    
        System.out.println(ar.get().getName());
        System.out.println(ar.get().getAge());
    
    }
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    class Person{
    
        int age;
        String name;
    }
    //dan
    //50 

    对象的属性修改类型

    如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。

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

    要想原子地更新对象的属性需要两步。

    1. 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
    2. 更新的对象属性必须使用 public volatile 修饰符。

    AtomicIntegerFieldUpdater常用方法的使用

    @Test
    public void AtomicIntegerFieldUpdateTest(){
        AtomicIntegerFieldUpdater<Person> a =
            AtomicIntegerFieldUpdater.newUpdater(Person.class,"age");
        Person p = new Person(18,"summer");
        System.out.println(a.getAndIncrement(p)); //18
        System.out.println(a.get(p)); //19
    }
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    class Person{
    
        public volatile int age;
        private String name;
    } 

    Java8新增的原子操作类

    • LongAdder

    由于AtomicLong通过CAS提供非阻塞的原子性操作,性能已经很好,在高并发下大量线程竞争更新同一个原子量,但只有一个线程能够更新成功,这就造成大量的CPU资源浪费。

    LongAdder 通过让多个线程去竞争多个Cell资源,来解决,再很高的并发情况下,线程操作的是Cell数组,并不是base,在cell元素不足时进行2倍扩容,在高并发下性能高于AtomicLong

    CAS的ABA问题的产生

    假设两个线程访问同一变量x。

    1. 第一个线程获取到了变量x的值A,然后执行自己的逻辑。
    2. 这段时间内,第二个线程也取到了变量x的值A,然后将变量x的值改为B,然后执行自己的逻辑,最后又把变量x的值变为A【还原】。
    3. 在这之后,第一个线程终于进行了变量x的操作,但此时变量x的值还是A,以为x的值没有变化,所以compareAndSet还是会成功执行。

    先来看一个值变量产生的ABA问题,理解一下ABA问题产生的流程:

    @SneakyThrows
    @Test
    public void test1() {
        AtomicInteger atomicInteger = new AtomicInteger(10);
    
        CountDownLatch countDownLatch = new CountDownLatch(2);
    
        new Thread(() -> {
            atomicInteger.compareAndSet(10, 11);
            atomicInteger.compareAndSet(11,10);
            System.out.println(Thread.currentThread().getName() + ":10->11->10");
            countDownLatch.countDown();
        }).start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                boolean isSuccess = atomicInteger.compareAndSet(10,12);
                System.out.println("设置是否成功:" + isSuccess + ",设置的新值:" + atomicInteger.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }).start();
    
        countDownLatch.await();
    }
    //输出:线程2并没有发现初始值已经被修改
    //Thread-0:10->11->10
    //设置是否成功true,设置的新值12 

    ABA问题存在,但可能对值变量并不会造成结果上的影响,但是考虑一种特殊的情况:

    https://zhuanlan.zhihu.com/p/237611535

    b15b467ef41bbd9be37abb5984ef6884.png
    1. 线程1和线程2并发访问ConcurrentStack。
    2. 线程1执行出栈【预期结果是弹出B,A成为栈顶】,但在读取栈顶B之后,被线程2抢占。
    3. 线程2记录栈顶B,依次弹出B和A,再依次将C,D,B入栈,且保证B就是原栈顶记录的B。
    4. 之后轮到线程1,发现栈顶确实是期望的B,遂弹出B,但此时栈顶已经是D,就出现了错误。

    BAB的问题如何解决

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

    @SneakyThrows
    @Test
    public void test2() {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(10,1);
    
        CountDownLatch countDownLatch = new CountDownLatch(2);
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(10, 11, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第二次版本:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(11, 10, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第三次版本:" + atomicStampedReference.getStamp());
            countDownLatch.countDown();
        }).start();
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
                boolean isSuccess = atomicStampedReference.compareAndSet(10,12, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 当前版本:" + atomicStampedReference.getStamp() + " 当前值:" + atomicStampedReference.getReference());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    
        countDownLatch.await();
    }
    //输出
    //输出
    Thread-0 第一次版本1
    Thread-0 第二次版本2
    Thread-0 第三次版本3
    Thread-1 第一次版本3
    Thread-1 修改是否成功true 当前版本4 当前值12

    而AtomicMarkableReference 通过标志位,标志位只有true和false,每次更新标志位的话,在第三次的时候,又会变得跟第一次一样,并不能解决ABA问题。

    @SneakyThrows
    @Test
    public void test3() {
        AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(10, false);
    
        CountDownLatch countDownLatch = new CountDownLatch(2);
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次标记:" + markableReference.isMarked());
            markableReference.compareAndSet(10, 11, markableReference.isMarked(), true);
            System.out.println(Thread.currentThread().getName() + " 第二次标记:" + markableReference.isMarked());
            markableReference.compareAndSet(11, 10, markableReference.isMarked(), false);
            System.out.println(Thread.currentThread().getName() + " 第三次标记:" + markableReference.isMarked());
            countDownLatch.countDown();
        }).start();
    
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 第一次标记:" + markableReference.isMarked());
            try {
                TimeUnit.SECONDS.sleep(2);
                boolean isSuccess = markableReference.compareAndSet(10,12, false, true);
                System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 当前标记:" + markableReference.isMarked() + " 当前值:" + markableReference.getReference());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    
        countDownLatch.await();
    }
    //输出
    Thread-0 第一次标记false
    Thread-0 第二次标记true
    Thread-0 第三次标记false
    Thread-1 第一次标记false
    Thread-1 修改是否成功true 当前标记true 当前值12

    如果觉得本文对你有帮助,可以点赞支持一下,一起学习进步

    最后

    Java程序员必备的一个Java核心知识点和程序员交流集中聚集地

    程序员必备 Java 核心知识点整理zhuanlan.zhihu.com
    c87c8226137f67beb9ff716d54e195f8.png
    Java程序员交流圈 - 知乎www.zhihu.com
    d36f46bca046c915e9ddcf3247cdf0e6.png

    a4bb14add896c4a91e0adc13d57227ff.png
    作者:一个帅气的人
    文章链接:https://segmentfault.com/a/1190000025131009
    来源: SegmentFault
    展开全文
  • 在Java中的并发包中了提供了以下种类型的原子类来来解决线程安全的问题。分为 基本数据类型原子类 数组类型原子类 引用类型原子类 字段类型原子类。 因为其内部原理都差不多一致。这里会对每种类型的原子类抽一...
  • 正点原子把STM32的烧录下载分为两种,一种是通过串口下载,一种是基于JTAG/SWD协议的,JLink STlink都属于这一,这种除了可以下载程序,还可以调试。 下面都是拍自《原子教你玩STM32库函数版》 ...
  • JUC-原子类(AtomicInteger)

    2018-10-08 13:12:28
    我们都知道i++在java中不是原子操作,因为i++的操作实际上分为三个步骤“读-改-写”, int i =10; i = i++; 实际上是分为下面步: int temp = i; i = i + 1; i = temp;   反编译后可以看到每条指令都会...
  • 这篇说说java.util.concurrent.atomic包里的,总共12个,网上有很多文章解析这,这里挑些重点说说。   这12个可以分为三组: 1. 普通类型的原子变量 2. 数组类型的原子变量 3. 域更新器   普通...
  • 网上有非常多文章解析这。这里挑些重点说说。 这12个能够分为三组: 1. 普通类型的原子变量 2. 数组类型的原子变量 3. 域更新器 普通类型的原子变量的6个, 1. 当中AtomicBoolean, ...
  • 因为i++不是原子操作,一个i++分为以下个步骤 i从内存中读x的值到寄存器中, 对寄存器加1, 再把新值写回x所处的内存地址, 当一个线程执行一半的时候,i的使用权被另外的线程拿去,那么得到就不是我们想要...
  • 第二章JDK源码剖析-并发篇第 8 节Atomic经过volatile和synchronize关键字的底层原理的洗礼,不知道你是否有一种感觉,要想弄明白它们的原理是一个非常难的过程。...JDK中的juc(并发包)的知识大体可以分为如下块: ...
  • 原子操作分为以下几类 对1字节的读写 对2字节数(对齐到16位边界)读写 对4字节数(对齐到32位边界)读写 对8字节数(对齐到64位边界)读写 xchg 原子操作基本原理 在x86平台上,CPU提供了在指令执行期间对...
  • 今天继续探索j.u.c中的12个原子操作Atomic,可以进行分为四组。基本类型、数组类型、引用类型、属性类型。这些都是采用Unsafe中的方法进行实现。基本类型Atomic提供以下...
  • 2019/07/13 作者/EWG1990仪器学习网更多的仪器学习资料,在仪器学习网原子吸收光谱仪(又称原子吸收分光光度计)由光源、原子化器、分光器、检测系统等部分组成。随着原子吸收光谱分析在工作中的广泛应用原子吸收...
  • 发射光谱仪按其发生的本质可分为原子发射光谱、离子发射光谱、分子发射光谱和X射线发射光谱等。原子发射光谱及离子发射光谱主要是由原子的价电子能级变化而产生的光谱;分子发射光谱仪是由分子中的电子能级及分子...
  • 一、纳米材料的分类纳米材料的分类方法很多,按其结构可分为:晶粒尺寸在三个方向都在个纳米范围内的称为三维纳米材料;具有层状结构的称为二维纳米材料;具有纤维结构的称为一维纳米材料;具有原子簇和原子束结构...
  • 红外探测器材料根据工作原理又可分为光子探测器材料和热探测器材料两大。 光子探测器村料是利用光子直接与电子相互作用产生光电交应的原理而工作的。由于电子可以被束绩在晶格原子周围,也可以是自由电子,因而...
  • 点击打开原文链接线程间的同步方法大体可分为:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户...
  • Java 高并发之无锁(CAS)本篇主要讲 Java中的无锁 ...1. Atomic 包java.util.concurrent.atomic 包下类都是原子类,原子类都是基于 sun.misc.Unsafe 实现的基本可以分为以下几类原子性基本数据类型:AtomicBoolea...
  • Java 高并发之无锁(CAS)本篇主要讲 Java中的...1. Atomic 包java.util.concurrent.atomic 包下类都是原子类,原子类都是基于 sun.misc.Unsafe 实现的基本可以分为一下几类原子性基本数据类型:AtomicBoolean、Ato...
  • Java 高并发之无锁(CAS)本篇主要讲 Java中的...1. Atomic 包java.util.concurrent.atomic 包下类都是原子类,原子类都是基于 sun.misc.Unsafe 实现的基本可以分为一下几类原子性基本数据类型:AtomicBoolean、Ato...
  • Java 高并发之无锁(CAS)本篇主要讲 Java中的...1. Atomic 包java.util.concurrent.atomic 包下类都是原子类,原子类都是基于 sun.misc.Unsafe 实现的基本可以分为一下几类原子性基本数据类型:AtomicBoolean、Ato...
  • AtomicInteger源码

    2016-06-11 13:45:44
    Atomic原子类,可以对相应的操作执行原子操作。主要分为以下种: 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ; 2. 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ; 3. ...
  • 概述 java.util.concurrent 包是专为 ...包下的所有可以分为如下: locks部分:显式锁(互斥锁和速写锁)相关; atomic部分:原子变量相关,是构建非阻塞算法的基础; executor部分:线程池相关; colle...

空空如也

空空如也

1 2 3 4 5
收藏数 89
精华内容 35
关键字:

原子分为几类