1. 程式人生 > >Java學習筆記—多線程(原子類,java.util.concurrent.atomic包,轉載)

Java學習筆記—多線程(原子類,java.util.concurrent.atomic包,轉載)

支持 位置 dset 賦值 嵌入 imp ans 匯編指令 sta

原子類

Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中
的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。
因為變量的類型有很多種,所以在Atomic包裏一共提供了13個類,屬於4種類型的原子更
新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(字段)。
Atomic包裏的類基本都是使用Unsafe實現的包裝類

java.util.concurrent.atomic中的類可以分成4組:

  • 標量類(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 復合變量類:AtomicMarkableReference,AtomicStampedReference

CAS

CAS(Compare-And-Swap)算法保證數據操作的原子性。

CAS 算法是硬件對於並發操作共享數據的支持。

CAS 包含了三個操作數:
  內存值 V
  預估值 A
  更新值 B

當且僅當 V == A 時,V 將被賦值為 B,否則循環著不斷進行判斷 V 與 A 是否相等。

CAS的全稱為Compare-And-Swap,是一條CPU的原子指令,其作用是讓CPU比較後原子地更新某個位置的值,經過調查發現,其實現方式是基於硬件平臺的匯編指令,就是說CAS是靠硬件實現的,JVM只是封裝了匯編調用,那些AtomicInteger類便是使用了這些封裝後的接口。
atomic包的原子包下使用這種方案。例如AtomicInteger的getAndIncrement自增+1的方法,經查看源碼如下:

public final int getAndIncrement() {
for (;;) {
int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

源碼中for循環體的第一步先取得AtomicInteger裏存儲的數值,第二步對AtomicInteger的當前數值進行加1操作,關鍵的第三步調用compareAndSet方法來進行原子更新操作,該方法先檢查當前數值是否等於current,等於意味著AtomicInteger的值沒有被其他線程修改過,則將AtomicInteger的當前數值更新成next的值,如果不等compareAndSet方法會返回false,程序會進入for循環重新進行compareAndSet操作

Unsafe只提供了3種CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,具體實現都是使用了native方法。

/**
* 如果當前數值是expected,則原子的將Java變量更新成x
* @return 如果更新成功則返回true
*/
public final native boolean compareAndSwapObject(Object o,
long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);

native關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前文件,而是在用其他語言(如C和C++)實現的文件中。Java語言本身不能對操作系統底層進行訪問和操作,但是可以通過JNI接口調用其他語言來實現對底層的訪問。

JNI是Java本機接口(Java Native Interface),是一個本機編程接口,它是Java軟件開發工具箱(java Software Development Kit,SDK)的一部分。JNI允許Java代碼使用以其他語言編寫的代碼和代碼庫。Invocation API(JNI的一部分)可以用來將Java虛擬機(JVM)嵌入到本機應用程序中,從而允許程序員從本機代碼內部調用Java代碼。

標量類

  • AtomicBoolean:原子更新布爾變量
  • AtomicInteger:原子更新整型變量
  • AtomicLong:原子更新長整型變量
  • AtomicReference:原子更新引用類型

具體到每個類的源代碼中,提供的方法基本相同,這裏以AtomicInteger為例進行說明,AtomicInteger源碼主要的方法如下,原理主要都要都是采用了CAS,這裏不再累述。

public class AtomicInteger extends Number implements java.io.Serializable {
   
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
 //返回當前的值
    public final int get() {
        return value;
    }
    //最終會設置成新值
    public final void set(int newValue) {
        value = newValue;
    }
    
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
      //原子更新為新值並返回舊值
    public final int getAndSet(int newValue) {
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
     //如果輸入的值等於預期值,則以原子方式更新為新值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

 //原子自增
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

//原子方式將當前值與輸入值相加並返回結果
    public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return current;
        }
    } 
}

數組類

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

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

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

以上幾個類提供的方法幾乎一樣,所以這裏以AtomicIntegerArray為例進行講解

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;
 

    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }

     
    public AtomicIntegerArray(int length) {
        array = new int[length];
    }

   
    public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }

    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }

    
    public final void set(int i, int newValue) {
        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
    }

   
    public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
    }

    
    public final int getAndSet(int i, int newValue) {
        long offset = checkedByteOffset(i);
        while (true) {
            int current = getRaw(offset);
            if (compareAndSetRaw(offset, current, newValue))
                return current;
        }
    }
 
    public final boolean compareAndSet(int i, int expect, int update) {
        return compareAndSetRaw(checkedByteOffset(i), expect, update);
    }

    private boolean compareAndSetRaw(long offset, int expect, int update) {
        return unsafe.compareAndSwapInt(array, offset, expect, update);
    }

     

}

這裏關鍵的實現也還是使用了CAS的策略,具體通過unsafe的native方法進行實現

原子更新字段類

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

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起
    來,可用於原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的
    ABA問題。
    要想原子地更新字段類需要兩步。第一步,因為原子更新字段類都是抽象類,每次使用的
    時候必須使用靜態方法newUpdater()創建一個更新器,並且需要設置想要更新的類和屬性。第
    二步,更新類的字段(屬性)必須使用public volatile修飾符
public class AtomicIntegerFieldUpdaterTest {
// 創建原子更新器,並設置需要更新的對象類和對象的屬性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) {
// 設置柯南的年齡是10歲
User conan = new User("conan", 10);
// 柯南長了一歲,但是仍然會輸出舊的年齡
System.out.println(a.getAndIncrement(conan));
// 輸出柯南現在的年齡
System.out.println(a.get(conan));
}
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;
}
}
}

代碼執行後輸出如下。

10
11

轉載自:

Java並發編程-原子類及並發工具類

Java學習筆記—多線程(原子類,java.util.concurrent.atomic包,轉載)