Java原子操作類匯總


  當程序更新一個變量時,如果是多線程同時更新這個變量,可能得到的結果與期望值不同。比如:有一個變量i,A線程執行i+1,B線程也執行i+1,經過兩個線程的操作后,變量i的值可能不是期望的3,而是2。這是因為,可能在A線程和B線程執行的時候拿到的i的值都是1,這就是線程不安全的更新操作,通常我們會使用synchronized來解決這個問題,synchronized能保證多線程不會同時更新變量i.

  從java1.5開始,jdk提供了java.util.concurrent.atomic包,這個包中的原子操作類,提供了一種用法簡單,性能高效,線程安全的更新一個變量的方式。

  atomic包里面一共提供了13個類,分為4種類型,分別是:原子更新基本類型,原子更新數組,原子更新引用,原子更新屬性,這13個類都是使用Unsafe實現的包裝類。

一.原子更新基本類型

  atomic提供了3個類用於原子更新基本類型:分別是AtomicInteger原子更新整形,AtomicLong原子更新長整形,AtomicBoolean原子更新bool值。由於這三個類提供的方法幾乎是一樣的,因此本節以AtomicInteger為例進行說明。

  AtomicInteger的常用方法有:

    1.int addAndGet(int delta):以原子的方式將輸入的值與實例中的值相加,並把結果返回

    2.boolean compareAndSet(int expect, int update):如果輸入值等於預期值,以原子的方式將該值設置為輸入的值

    3.final int getAndIncrement():以原子的方式將當前值加1,並返回加1之前的值

    4.void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值后,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。

    5.int getAndSet(int newValue):以原子的方式將當前值設置為newValue,並返回設置之前的舊值

  AtomicInteger的使用例子如下:

public class AtomicIntegerTest {

    private static AtomicInteger atomicInteger = new AtomicInteger(1);

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

}
View Code

  輸出結果如下:

1
2
2
5

  getAndIncremen是如何實現原子操作的呢?讓我們看一下getAndIncremen的實現代碼,並分析實現原理。getAndIncremen的實現代碼如下:  

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

  我們可以看到getAndIncrement調用了unsafe的getAndAddInt,getAndAddInt的實現:

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

  getAndAddInt調用了Unsafe的native方法:getIntVolatile和compareAndSwapInt,在do-while循環中先取得當前值,然后通過CAS判斷當前值是否和current一致,如果一致意味着值沒被其他線程修改過,把當前值設置為當前值+var4,,如果不相等程序進入信的CAS循環。

  由於atomic只提供了int,long和boolean的原子操作類,那么其他的基本類型,如byte,char,float,double如何實現原子操作呢,原子操作都是通過Unsafe實現的,讓我們看一下Unsafe的實現

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

  通過代碼,我們發現Unsafe只提供了3種CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發現它是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,所以原子更新char、float和double變量也可以用類似的思路來實現。  

二.原子更新數組

  atomic里提供了三個類用於原子更新數組里面的元素,分別是:

  AtomicIntegerArray:原子更新整形數組里的元素

  AtomicLongArray:原子更新長整形數組里的元素

  AtomicReferenceArray:原子更新引用數組里的元素

  因為每個類里面提供的方法都一致,因此以AtomicIntegerArray為例來說明。AtomicIntegerArray主要提供了以原子方式更新數組里的整數,常見方法如下:

  int addAndGet(int i, int delta):以原子的方式將輸入值與數組中索引為i的元素相加

  boolean compareAndSet(int i, int expect, int update):如果當前值等於預期值,則以原子方式將數組位置i的元素設置成update值。

  實例如下:

public class AtomicIntegerArrayTest {

    private static int[] value = new int[]{1,2,3};
    private static AtomicIntegerArray atomicInteger = new AtomicIntegerArray(value);

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

}

  需要注意的是,數組value通過構造方法傳遞進去,然后AtomicIntegerArray會將當前數組復制一份,所以當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組。

三.原子更新引用

  原子更新基本類型的AtomicInteger只能更新一個變量,如果要原子更新多個變量,就需要使用原子更新引用類型提供的類了。原子引用類型atomic包主要提供了以下幾個類:

  AtomicReference:原子更新引用類型

  AtomicReferenceFieldUpdater:原子更新引用類型里的字段

  AtomicMarkableReference:原子更新帶有標記位的引用類型。可以原子更新一個布爾類型的標記位和引用類型。構造方法是AtomicMarkableReference(V initialRef,booleaninitialMark)

  以上類中提供的方法基本一致,我們以AtomicReference為例說明:

  

public class AtomicReferenceTest {

    private static AtomicReference<User> reference = new AtomicReference<User>();

    public static void main(String[] args){
        User user = new User("tom",23);
        reference.set(user);
        User updateUser = new User("ketty",34);
        reference.compareAndSet(user,updateUser);
        System.out.println(reference.get().getName());
        System.out.println(reference.get().getAge());
    }


    static class User{

        private String name;
        private int age;

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

        public String getName() {
            return name;
        }

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

  上述代碼中首先創建一個user對象,然后把user對象設置進AtomicReference中,最后通過compareAndSet做原子更新操作,運行結果如下:

ketty
34

四.原子更新屬性

  如果需要原子更新某個對象的某個字段,就需要使用原子更新屬性的相關類,atomic中提供了一下幾個類用於原子更新屬性:

  AtomicIntegerFieldUpdater:原子更新整形屬性的更新器

  AtomicLongFieldUpdater:原子更新長整形的更新器

  AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用於原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的ABA問題。

  想要原子的更新字段,需要兩個步驟:

  1.因為原子更新字段類都是抽象類,每次使用的時候必須使用靜態方法newUpdater()創建一個更新器,並且需要設置想要更新的類和屬性

  2.更新類的字段(屬性)必須使用public volatile修飾符

  本節僅以AstomicIntegerFieldUpdater為例進行講解,AstomicIntegerFieldUpdater的示例代碼如下:

  

public class AtomicIntegerFieldUpdaterTest {

    //創建原子更新器
    private static AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

    public static void main(String[] args){
        User user = new User("ketty",21);
        //ketty長了一歲
        updater.getAndIncrement(user);
        System.out.println(updater.get(user));
    }

    static class User{

        private String name;
        public volatile int age;

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

        public String getName() {
            return name;
        }

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

  如果age的修飾符號不是 public volatile,回報一下錯誤:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: java.lang.IllegalAccessException: Class com.ysl.atomic.AtomicIntegerFieldUpdaterTest can not access a member of class com.ysl.atomic.AtomicIntegerFieldUpdaterTest$User with modifiers "private"
    at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:405)
    at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
    at com.ysl.atomic.AtomicIntegerFieldUpdaterTest.<clinit>(AtomicIntegerFieldUpdaterTest.java:8)
Caused by: java.lang.IllegalAccessException: Class com.ysl.atomic.AtomicIntegerFieldUpdaterTest can not access a member of class com.ysl.atomic.AtomicIntegerFieldUpdaterTest$User with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at sun.reflect.misc.ReflectUtil.ensureMemberAccess(ReflectUtil.java:103)
    at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:394)
    ... 2 more

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM