高併發學習之08原子操作類

1. 簡介

在前面文章synchronized中,我們介紹了synchonized 輕量級鎖時,談到其是CAS機制實現的,本文我們將討論JDK爲我們提供的13種原子操作類,他們的實現原理都是CAS機制。13種原子類在java.util.concurrent.atomic包中。

2. 原子更新基本類型類

2.1.原子更新基本類型類

使用原子的方式更新基本類型,Atomic包提供了以下3個類。

  • AtomicBoolean:原子更新布爾類型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新長整型。

以上3個類提供的方法幾乎一模一樣,所以本節僅以AtomicInteger爲例進行講解,AtomicInteger的常用方法如下:

  • int addAndGet(int delta):以原子方式將輸入的數值與實例中的值(AtomicInteger裏的value)相加,並返回結果。
  • boolean compareAndSet(int expect,int update):如果輸入的數值等於預期值,則以原子方式將該值設置爲輸入的值。
  • int getAndIncrement():以原子方式將當前值加1,注意,這裏返回的是自增前的值。
  • void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值後,可能導致其他
    線程在之後的一小段時間內還是可以讀到舊的值。
  • int getAndSet(int newValue):以原子方式設置爲newValue的值,並返回舊值。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
	static AtomicInteger ai = new AtomicInteger(1);
	public static void main(String[] args) {
		System.out.println(ai.getAndIncrement());
		System.out.println(ai.get());
	}
}
2.1.2 原子類實現原理

爲了搞懂其原子類操作的原理,下面以AtomicInteger中getAndSet()方法爲了例:

private static final Unsafe unsafe = Unsafe.getUnsafe();
public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

從上面的源碼可以看出,其調用unsafe類中getAndSetInt方法。unsafe是sun.misc包下的工具類,原子類中操作都是調用unsafe中的方法。其源碼爲:

public native int getIntVolatile(Object var1, long var2);

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

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

        return var5;
    }

使用do。。while方式判斷,獲取到的當前值與內存中當前值是否一致,如果一致則更新,不一致在重新獲取。注意native 修飾的方法都是JVM底層方法,直接調用c++函數。關於native解釋可以看下這篇文章native解釋
通過代碼,我們發現Unsafe只提供了3種CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發現它是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,所以原子更新char、float和double變量也可以用類似的思路來實現。

3. 原子更新數組

通過原子的方式更新數組裏的某個元素,Atomic包提供了以下3個類:

  • AtomicIntegerArray:原子更新整型數組裏的元素
  • AtomicLongArray:原子更新長整型數組裏的元素
  • AtomicReferenceArray:原子更新引用類型數組裏的元素。

AtomicIntegerArray類主要是提供原子的方式更新數組裏的整型,其常用方法如下:

  • int addAndGet(int i,int delta):以原子方式將輸入值與數組中索引i的元素相加
  • boolean compareAndSet(int i,int expect,int update):如果當前值等於預期值,則以原子方式將數組位置i的元素設置成update值。

以上幾個類提供的方法幾乎一樣,舉個例子:

public class AtomicIntegerArrayTest {
	static int[] value = new int[] { 12 };
	static AtomicIntegerArray ai = new AtomicIntegerArray(value);
	public static void main(String[] args) {
		ai.getAndSet(03);
		System.out.println(ai.get(0));
		System.out.println(value[0]);
	}
}
輸出結果:
3
1

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

4. 原子更新引用類型

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

  • AtomicReference:原子更新引用類型。
  • AtomicReferenceFieldUpdater:原子更新引用類型裏的字段。
  • AtomicMarkableReference:原子更新帶有標記位的引用類型。可以原子更新一個布爾類型的標記位和引用類型。構造方法是 public AtomicMarkableReference(V initialRef, boolean initialMark) ;
    以上幾個類提供的方法幾乎一樣,所以本節僅以AtomicReference爲例進行講解,AtomicReference的使用示例代碼如下所示。
public class AtomicReferenceTest {
	public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
    public static void main(String[] args) {
        User user = new User("herman",15);
        atomicUserRef.set(user);
        User updateUser = new User("ocan",17);
        atomicUserRef.compareAndSet(user, updateUser);
        System.out.println(atomicUserRef.get().getName());
        System.out.println(atomicUserRef.get().getOld());
    }
    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;
        }
    }
}
輸出結果如下:
ocan
17

代碼中首先構建一個user對象,然後把user對象設置進AtomicReferenc中,最後調用compareAndSet方法進行原子更新操作,實現原理同AtomicInteger裏的compareAndSet方法。

5. 原子更新字段類

如果需原子地更新某個類裏的某個字段時,就需要使用原子更新字段類,Atomic包提供了以下3個類進行原子字段更新:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型。

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

要想原子地更新字段類需要兩步:

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

上面3個類提供的方法幾乎一樣,所以僅以AstomicIntegerFieldUpdater爲例進行講解,

public class AtomicIntegerFieldUpdaterTest {
	// 創建原子更新器,並設置需要更新的對象類和對象的屬性
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
            newUpdater(User.class, "age");
    public static void main(String[] args) {
        // 設置ocan age爲10
        User conan = new User("ocan", 10);
        // age原子自增,輸出上一次值
        System.out.println(a.getAndIncrement(conan));
        // 輸出自增後的age
        System.out.println(a.get(conan));
    }
    public 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;
        }
    }
}
代碼執行後輸出如下。
10
11
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章