1. 程式人生 > 實用技巧 >原子操作類

原子操作類

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;
    }
}