1. 程式人生 > >Java併發程式設計系列之十九:原子操作類

Java併發程式設計系列之十九:原子操作類

原子操作類簡介

當更新一個變數的時候,多出現資料爭用的時候可能出現所意想不到的情況。這時的一般策略是使用synchronized解決,因為synchronized能夠保證多個執行緒不會同時更新該變數。然而,從jdk 5之後,提供了粒度更細、量級更輕,並且在多核處理器具有高效能的原子操作類。因為原子操作類把競爭的範圍縮小到單個變數上,這可以算是粒度最細的情況了。

原子操作類相當於泛化的volatile變數,能夠支援原子讀取-修改-寫操作。比如AtomicInteger表示一個int型別的數值,提供了get和set方法,這些volatile型別的變數在讀取與寫入上有著相同的記憶體語義。原子操作類共有13個類,在java.util.concurrent.atomic包下,可以分為四種類型的原子更新類:原子更新基本型別、原子更新陣列型別、原子更新引用和原子更新屬性。

下面將分別介紹這四種原子操作類。

原子更新基本型別

使用原子方式更新基本型別,共包括3個類:

  • AtomicBoolean:原子更新布林變數
  • AtomicInteger:原子更新整型變數
  • AtomicLong:原子更新長整型變數

具體到每個類的原始碼中,提供的方法基本相同,這裡以AtomicInteger為例進行說明。AtomicInteger提供的部分方法如下:

public class AtomicInteger extends Number implements java.io.Serializable {
    //返回當前的值
    public final int get
() { return value; } //原子更新為新值並返回舊值 public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } //最終會設定成新值 public final void lazySet(int newValue) { unsafe.putOrderedInt(this, valueOffset, newValue); } //如果輸入的值等於預期值,則以原子方式更新為新值
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //原子自增 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } //原子方式將當前值與輸入值相加並返回結果 public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } }

為了說明AtomicInteger的原子性,這裡程式碼演示多執行緒對一個int值進行自增操作,最後輸出結果,程式碼如下:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicIntegerDemo {

    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args){
        for (int i = 0; i < 5; i++){
            new Thread(new Runnable() {
                public void run() {
                    //呼叫AtomicInteger的getAndIncement返回的是增加之前的值
                    System.out.println(atomicInteger.getAndIncrement());
                }
            }).start();
        }
        System.out.println(atomicInteger.get());
    }
}

輸出結果如下:

0
1
2
3
4
5
5

可以看到在多執行緒的情況下,得到的結果是正確的,但是如果僅僅使用int型別的成員變數則可能得到不同的結果。這裡的關鍵在於getAndIncrement是原子操作,那麼是如何保證的呢?

getAndIncrement方法的原始碼如下:

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

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

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

到這裡可以發現最終呼叫了native方法來保證更新的原子性。

原子更新陣列

通過原子更新數組裡的某個元素,共有3個類:

  • AtomicIntegerArray:原子更新整型陣列的某個元素
  • AtomicLongArray:原子更新長整型陣列的某個元素
  • AtomicReferenceArray:原子更新引用型別陣列的某個元素

AtomicIntegerArray常用的方法有:

  • int addAndSet(int i, int delta):以原子方式將輸入值與陣列中索引為i的元素相加
  • boolean compareAndSet(int i, int expect, int update):如果當前值等於預期值,則以原子方式更新陣列中索引為i的值為update值

示例程式碼如下:

    package com.rhwayfun.concurrency.r0405;

    import java.util.concurrent.atomic.AtomicIntegerArray;

    /**
     * Created by rhwayfun on 16-4-5.
     */
    public class AtomicIntegerArrayDemo {

    static int[] value = new int[]{1, 2};

    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

陣列value通過構造的方式傳入AtomicIntegerArray中,實際上AtomicIntegerArray會將當前陣列拷貝一份,所以在陣列拷貝的操作不影響原陣列的值。

原子更新引用型別

需要更新引用型別往往涉及多個變數,早atomic包有三個類:

  • AtomicReference:原子更新引用型別
  • AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位
  • AtomicMarkableReference:原子更新帶有標記位的引用型別。

下面以AtomicReference為例進行說明:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicReferenceDemo {

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

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

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

    public static AtomicReference<User> ar = new AtomicReference<User>();

    public static void main(String[] args){
        User user = new User("aa",11);
        ar.set(user);
        User newUser = new User("bb",22);
        ar.compareAndSet(user,newUser);
        System.out.println(ar.get().getName());
        System.out.println(ar.get().getId());
    }
}

執行結果為:

bb
22

可以看到user被成功更新。

原子更新欄位類

如果需要原子更新某個類的某個欄位,就需要用到原子更新欄位類,可以使用以下幾個類:

  • AtomicIntegerFieldUpdater:原子更新整型欄位
  • AtomicLongFieldUpdater:原子更新長整型欄位
  • AtomicStampedReference:原子更新帶有版本號的引用型別。

要想原子更新欄位,需要兩個步驟:

  1. 每次必須使用newUpdater建立一個更新器,並且需要設定想要更新的類的欄位
  2. 更新類的欄位(屬性)必須為public volatile

下面的程式碼演示如何使用原子更新欄位類更新欄位:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicIntegerFieldUpdaterDemo {

    //建立一個原子更新器
    private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(User.class,"old");

    public static void main(String[] args){
        User user = new User("Tom",15);
        //原來的年齡
        System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
        //現在的年齡
        System.out.println(atomicIntegerFieldUpdater.get(user));
    }

    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 void setName(String name) {
            this.name = name;
        }

        public int getOld() {
            return old;
        }

        public void setOld(int old) {
            this.old = old;
        }
    }
}

輸出的結果如下:

15
16

至此,我們知道了如何使用原子操作類在不同場景下的基本用法。