原子操作类

package JvmTest;

import java.util.concurrent.atomic.AtomicInteger;

public class twelve {
    static AtomicInteger ai = new AtomicInteger(1);

    public static void main(String[] args) {
        System.out.println(ai.getAndIncrement());
        System.out.println(ai.get());
    }

    public final int getAndIncrement() {
        for (; ; ) {
            int current = ai.get(); //自增前的值
            int next = current + 1; //自增后的值
            if (ai.compareAndSet(current, next)) //判断AtomicInteger的当前数值是否等于current;
                // 如果真,则将AtomicInteger的当前值更新成next值
                return current; //但是getAndIncrement返回的仍然是current
        }
    }

    public final boolean compareAndSet(int expect ,int update) {
        //return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

 

package JvmTest;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class twelve {
    static int[] value = new int[]{1,2};
    //数组value作为参数传入,AtomicIntegerArray会将其复制一份,但是不会影响传入数组
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        ai.getAndSet(0,3);
        System.out.println(ai.get(0));
        System.out.println(value[0]);
    }
}
3
1

 

原子更新引用类型

package JvmTest;

import java.util.concurrent.atomic.AtomicReference;
//这个AtomicReference中的compareAndSet方法和AtomicInteger中的compareAndSet相似
public class twelve {
    public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();

    static class User   {
        private String name;
        private int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }
    public static void main(String[] args) {
         User user = new User("conan", 15);
         User updateUser = new User("Xia Linxi",18);

         atomicUserRef.set(user);
         atomicUserRef.compareAndSet(user, updateUser); //更新

         System.out.println("Name is:" + atomicUserRef.get().getName());
         System.out.println("Age is:" + atomicUserRef.get().getOld());
    }
}
Name is: Xia Linxi
Age is: 18

  

原子更新字段类

原子地更新某个类里的某个字段

package JvmTest;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class twelve {
    //创建原子更新器,并设置需要更新的对象类和对象的属性
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");

    public static class User   {
        private String name;
        public volatile int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }
    public static void main(String[] args) {
        //设置柯南的年龄是10岁
        User conan = new User("conan", 15);
        //柯南长了一岁,燃石仍然会输出旧的年龄
        System.out.println("之前的年龄: " + a.getAndIncrement(conan));
        //输出了柯南现在的年龄
        System.out.println("之后的年龄: "+ a.get(conan));
    }
}
之前的年龄: 15
之后的年龄: 16

  

一、Atomic原子类介绍

Atomic翻译成中文是原子的意思。化学上原子是构成一般物质的最小单位。这里Atomic是指一个操作不可以中断的(即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰)。所以,原子类简单点说就是具有原子/原子操作特征的类。

 

并发包JUC(java.util.concurrent)的原子类都存放在java.util.concurrent.atomic下,如图所示:

根据操作的数据类型,可以将JUC包中的原子类分为4类。

  • 基本类型

   使用原子的方式更新基本类型

  1. AtomicInteger:整型原子类
  2. AtomicLong:长整型原子类
  3. AtomicBoolean:布尔类型原子类
  • 数组类型

   使用原子的方式更新数组里的某个元素

  1. AtomicIntegerArray:整型数组原子类
  2. AtomicLongArray:长整型数组原子类
  3. AtomicReferenceArray:引用类型数组原子类
  • 引用类型

   原子地更新引用类型提供的类

  1. AtomicReference:引用类型原子类
  2. AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来
  3. AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
  • 对象的属性修改类型
  1. AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  2. AtomicLongFieldUpdater:原子更新长整型字段的更新器
  3. AtomicReferenceFieldUpdater:原子更新引用类型里的字段

注意:AtomicMarkableReference不能解决ABA问题

关于ABA问题:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

例子描述:年初,现金为零,然后通过正常劳动赚了三百万,之后正常消费了(比如买房子)三百万。年末,虽然现金零收入(可能变成其他形式了),但是赚了钱是事实,还是得交税的!

例子代码:

package JvmTest;
//乐观锁
import java.util.concurrent.atomic.AtomicInteger;

public class twelve {
    public static void main(String[] args) {
        defectOfABA();
    }

    public static void defectOfABA() {
        final AtomicInteger atomicInteger = new AtomicInteger(1);

        Thread coreThread = new Thread(
                ()->{
                final int currentValue = atomicInteger.get();
                //System.out.println(Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + "------------拿到了currentValue的值,然后去处理其他的业务");

                //模拟处理其他业务花费的时间
                try{
                    Thread.sleep(300);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }

                boolean casResult = atomicInteger.compareAndSet(1,2);
                System.out.println(Thread.currentThread().getName()
                + "------------currentValue = " + currentValue
                + ", finalValue = " + atomicInteger.get()
                + ", compareAndSet Result = " + casResult);
            }
        );
        coreThread.start();

        //让coreThread线程先跑起来
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread amateurThread = new Thread(
            ()->{
                int currentValue = atomicInteger.get();
                boolean casResult = atomicInteger.compareAndSet(1,2);
                System.out.println(Thread.currentThread().getName()
                + "------------currentValue = " + currentValue
                + ",finalValue = " + atomicInteger.get()
                + ",compareAndSet Result = " + casResult);

                currentValue = atomicInteger.get();
                casResult = atomicInteger.compareAndSet(2,1);
                System.out.println(Thread.currentThread().getName()
                        + "------------currentValue = " + currentValue
                        + ", finalValue = " + atomicInteger.get()
                        + ", compareAndSet Result = " + casResult);
            }
        );
        amateurThread.start();
    }
}
Thread-0------------ 拿到了currentValue的值,然后去处理其他的业务
Thread-1------------currentValue = 1,finalValue = 2,compareAndSet Result = true
Thread-1------------currentValue = 2, finalValue = 1, compareAndSet Result = true
Thread-0------------currentValue = 1, finalValue = 2, compareAndSet Result = true

Thread-0一开始取到了currentValue的值,但是没有处理 而是去做其他的业务。Thread-1在此期间修改了变量的值,但是最终又重新复原了。

 

package JvmTest;
/*AtomicMarkableReference是将一个boolean值作为是否更改的标记,本质就是他的版本号只有两个-true/false*/
/*修改的时候在这两个版本号之间来回切换,不能解决ABA问题,只会降低ABA问题发生的几率*/
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;

public class two_one {
    private static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100,false);

    public static void main(String[] args) {
        Thread refT1 = new Thread(()-> {
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("refT1的标志位: " + atomicMarkableReference.isMarked() + " initialRef is " + atomicMarkableReference.getReference());
            atomicMarkableReference.compareAndSet(100,101,atomicMarkableReference.isMarked(), !atomicMarkableReference.isMarked());
            System.out.println("refT1的标志位: " + atomicMarkableReference.isMarked() + " initialRef is " + atomicMarkableReference.getReference());
            atomicMarkableReference.compareAndSet(101,100,atomicMarkableReference.isMarked(), !atomicMarkableReference.isMarked());
            System.out.println("refT1的标志位: " + atomicMarkableReference.isMarked() + " initialRef is " + atomicMarkableReference.getReference());
        });

        Thread refT2 = new Thread(()-> {
            boolean marked = atomicMarkableReference.isMarked();
            System.out.println("refT2的标志位: " + marked +" initialRef is " + atomicMarkableReference.getReference());

            try{
                TimeUnit.SECONDS.sleep(2);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            boolean c3 = atomicMarkableReference.compareAndSet(100,101, marked, !marked);
            System.out.println("refT2的标志位: " + c3 + " initialRef is " + atomicMarkableReference.getReference());
        });

        refT1.start();
        refT2.start();
    }
}
refT2的标志位: false initialRef is 100
refT1的标志位: false initialRef is 100
refT1的标志位: true initialRef is 101
refT1的标志位: false initialRef is 100
refT2的标志位: true initialRef is 101

从这个例子可以证实,AtomicMarkableReference不能解决ABA问题。

JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

 

基本类型原子类的介绍

使用原子的方式更新基本类型

  1. AtomicInteger:整型原子类
  2. AtomicLong:长整型原子类
  3. AtomicBoolean:布尔类型原子类

上面三个类提供的方法几乎相同,所以这里以AtomicInteger为例子来介绍。

AtomicInteger类常用方法

package JvmTest;

import java.util.concurrent.atomic.AtomicInteger;

//测试AtomicInteger类的常用方法
public class two_two {
    public static void main(String[] args) {
        int temvalue = 0;
        AtomicInteger i = new AtomicInteger(0);

        temvalue = i.get(); //获取当前的值
        System.out.println("get():\t\ttemvalue: " + temvalue + ";\ti: " + i);

        temvalue = i.getAndSet(3);  //获取当前的值,并设置新的值
        System.out.println("getAndSet()\t\ttemvalue: " + temvalue + ";\ti: " + i);

        temvalue = i.getAndIncrement(); //获取当前的值,并自增
        System.out.println("getAndIncrement()\t\ttemvalue: " + temvalue + ";\ti: " + i);

        temvalue = i.getAndDecrement(); //获取当前的值,并自减
        System.out.println("getAndDecrement()\t\ttemvalue: " + temvalue + ";\ti: " + i);

        temvalue = i.getAndAdd(3);  //获取当前的值,并加上预期的值
        System.out.println("getAndAdd()\t\ttemvalue: " + temvalue + ";\ti: " + i);

        boolean flag = i.compareAndSet(6,5);    //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
        System.out.println("compareAndSet()\t\t flag: " + flag);

    }
}

 

基本数据类型原子类的优势

多线程环境使用原子类AtomicInteger类来保证线程安全(基本数据类型),从而避免synchronized的高开销,执行效率大大提高。

AtomicInteger类主要利用CAS(compare and swap)+volatile + native方法来保证原子操作。

    // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。

 

数组类型原子类介绍

使用原子的方式更新数组里的某个元素

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

上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。

public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
package JvmTest;
import java.util.concurrent.atomic.AtomicIntegerArray;

//测试AtomicIntegerArray类的常用方法使用
public class two_two {
    public static void main(String[] args) {
        int temvalue = 0;
        int[] nums = {1,2,3,4,5,6};

        AtomicIntegerArray i = new AtomicIntegerArray(nums);
        for(int j = 0; j < nums.length; j++) {
            System.out.println("nums的第" + j +"个下标的值为:"+ i.get(j));
        }

        temvalue = i.getAndSet(0,2);
        System.out.println("temvalue:" + temvalue + ";\ti:" + i);

        temvalue = i.getAndIncrement(0);
        System.out.println("temvalue:" + temvalue + ";\ti:" + i);

        temvalue = i.getAndAdd(0,5);
        System.out.println("temvalue:" + temvalue + ";\ti:" + i);

    }
}
nums的第0个下标的值为:1
nums的第1个下标的值为:2
nums的第2个下标的值为:3
nums的第3个下标的值为:4
nums的第4个下标的值为:5
nums的第5个下标的值为:6
temvalue:1;	i:[2, 2, 3, 4, 5, 6]
temvalue:2;	i:[3, 2, 3, 4, 5, 6]
temvalue:3;	i:[8, 2, 3, 4, 5, 6]

 

引用类型原子类介绍

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

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
  • AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,boolean只能表示true/false。

上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。

package JvmTest;

import java.util.concurrent.atomic.AtomicReference;

//测试AtomicReference类的常用方法使用
public class two_two {
    public static void main(String[] args) {
        AtomicReference<Person> per = new AtomicReference<Person>();

        Person person = new Person("Xia",18);
        per.set(person);

        Person updateperson = new Person("XX",20);
        per.set(updateperson);

        System.out.println(per.compareAndSet(updateperson, person));

        System.out.println(per.get().getName());
        System.out.println(per.get().getAge());
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }

    public void setAge(int age){
        this.age = age;
    }
}

上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:

true
Xia
18

 

AtomicStampedReference类使用示例 这里给出各种API的博客

package JvmTest;

import com.sun.deploy.util.SyncAccess;

import java.util.concurrent.atomic.AtomicStampedReference;

//AtomicStampedReference类使用示例
public class two_two {
    public static void main(String[] args) {
        //实例化、取当前值和stamp值
        final Integer initialRef = 3, initialStamp = 0;
        final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef,initialStamp);
        System.out.println("currentValue = " + asr.getReference() + "; currentStamp = " + asr.getStamp());

        //CAS(Compare and Set)
        final Integer newReference = 666, newStamp = 999;
        final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);
        System.out.println("currentValue = " + asr.getReference()
                        + ", currentStamp = " + asr.getStamp()
                        + ", casResult = " + casResult);

        //获取当前的值和当前的stamp值
        int[] arr = new int[1];
        final Integer currentValue = asr.get(arr);
        final int currentStamp = arr[0];
        System.out.println("currentValue = " + currentValue + ", currentStamp = " + currentStamp);

        //单独设置stamp值
        final boolean attemptStampResult = asr.attemptStamp(newReference, 88);
        System.out.println("currentValue = " + asr.getReference()
                + ", currentStamp = " + asr.getStamp()
                + ", attemptStampResult = " + attemptStampResult);

        //重新设置当前值和stamp值
        asr.set(initialRef, initialStamp);
        System.out.println("currentValue = " + asr.getReference() + ", currentStamp = " + asr.getStamp());

        //asr.weakCompareAndSet()不建议使用
    }
}
currentValue = 3; currentStamp = 0
currentValue = 666, currentStamp = 999, casResult = true
currentValue = 666, currentStamp = 999
currentValue = 666, currentStamp = 88, attemptStampResult = true
currentValue = 3, currentStamp = 0

  

AtomicMarkableReference类使用示例 

package JvmTest;


import java.util.concurrent.atomic.AtomicMarkableReference;

//AtomicMarkableReference类使用示例
public class two_two {
    public static void main(String[] args) {
        //实例化、取当前值和mark值
        final Boolean initialRef = false, initialMark = true;
        final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);
        System.out.println("currentValue = " + amr.getReference() + "; currentMark = " + amr.isMarked());

        //CAS(compare and swap)
        final Boolean newRef = true, newMark = true;
        final boolean casResult = amr.compareAndSet(initialRef, newRef, initialMark, newMark);
        System.out.println("currentValue = " + amr.getReference()
            + ", currentMark = " + amr.isMarked()
            + ", casResult = " + casResult);

        //获取当前值的当前的mark值
        boolean[] arr = new boolean[1];
        final Boolean currentValue = amr.get(arr);
        final boolean currentMark = arr[0];
        System.out.println("currentValue = " + currentValue
                + ", currentMark = " + currentMark);

        //单独设置mark值
        final boolean attemptMarkResult = amr.attemptMark(newRef, false);
        System.out.println("currentValue = " + amr.getReference()
                + ", currentMark = " + amr.isMarked()
                + ", casResult = " + attemptMarkResult);

        //重新设置当前值和mark值
        amr.set(initialRef, initialRef);
        System.out.println("currentValue = " + amr.getReference() + ", currentMark = " + amr.isMarked());
    }
}
currentValue = false; currentMark = true
currentValue = true, currentMark = true, casResult = true
currentValue = true, currentMark = true
currentValue = true, currentMark = false, casResult = true
currentValue = false, currentMark = false

 

对象的属性修改类型原子类

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

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

想要原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器

并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用public volatile修饰

package JvmTest;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class Eight {
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

        User user = new User("Xia",22);
        System.out.println(a.getAndIncrement(user));    //令user的age属性+1, 返回的是22, new值是23
        System.out.println(a.getAndSet(user,5));
        System.out.println(a.get(user));
    }
}

class User{
    private String name;
    public volatile int age;

    public User(String name, int age){
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age){
        this.age = age;
    }
}

  

 

posted @ 2020-12-19 17:21  Peterxiazhen  阅读(217)  评论(0编辑  收藏  举报