天天看點

Java高并發程式設計:原子類

1. 并發程式設計概念

原子性

一個操作不能被再拆分了;即一個操作或者多個操作 要麼全部執行并且執行的過程不會被任何因素打斷,要麼就都不執行。一個很經典的例子就是銀行賬戶轉賬問題。

增量操作符++,不是原子的操作,它是先讀取舊值,然後寫回新值,包含2個操作

可見性

可見性是指當多個線程通路同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

有序性

即程式執行的順序按照代碼的先後順序執行。但是指令重排序會影響到線程并發執行的正确性

總結

要想并發程式正确地執行,必須要保證原子性、可見性以及有序性。隻要有一個沒有被保證,就有可能會導緻程式運作不正确。

并發,并行,串行

并行是邏輯上同時發生,指在某一個時間内同時運作多個程式。

并發是實體上同時發生,指在某一個時間點同時運作多個程式。

2. 原子類

java.util.concurrent.atomic包:原子類的小工具包,支援在單個變量上解除鎖的線程安全程式設計

2.1 如果同一個變量要被多個線程通路,則可以使用該包中的類

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
  • AtomicReference

2.2 AtomicIntegerArray:操作數組裡面的某個整數

2.3 同樣該包中提供了可以用于反射操作的類

  • AtomicReferenceFieldUpdater
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

它們可以提供對關聯字段類型的通路。

3. 原子性布爾 AtomicBoolean

AtomicBoolean 類為我們提供了一個可以用原子方式進行讀和寫的布爾值,它還擁有一些先進的原子性操作,比如 compareAndSet()。AtomicBoolean 類位于 java.util.concurrent.atomic 包,完整類名是為 java.util.concurrent.atomic.AtomicBoolean。本小節描述的 AtomicBoolean 是 Java 8 版本裡的,而不是它第一次被引入的 Java 5 版本。

AtomicBoolean 背後的設計理念在我的《Java 并發指南》主題的《比較和交換》小節有解釋。

建立一個 AtomicBoolean

你可以這樣建立一個 AtomicBoolean:

AtomicBoolean atomicBoolean = new AtomicBoolean();  
           

以上示例建立了一個預設值為 false 的 AtomicBoolean。

如果你想要為 AtomicBoolean 執行個體設定一個顯式的初始值,那麼你可以将初始值傳給 AtomicBoolean 的構造子:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  
           

擷取 AtomicBoolean 的值

你可以通過使用 get() 方法來擷取一個 AtomicBoolean 的值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

boolean value = atomicBoolean.get();  
           

以上代碼執行後 value 變量的值将為 true。

設定 AtomicBoolean 的值

你可以通過使用 set() 方法來設定一個 AtomicBoolean 的值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

atomicBoolean.set(false);  
           

以上代碼執行後 AtomicBoolean 的值為 false。

交換 AtomicBoolean 的值

你可以通過 getAndSet() 方法來交換一個 AtomicBoolean 執行個體的值。getAndSet() 方法将傳回 AtomicBoolean 目前的值,并将為 AtomicBoolean 設定一個新值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  
boolean oldValue = atomicBoolean.getAndSet(false);  
           

以上代碼執行後 oldValue 變量的值為 true,atomicBoolean 執行個體将持有 false 值。代碼成功将 AtomicBoolean 目前值 ture 交換為 false。

比較并設定 AtomicBoolean 的值

compareAndSet() 方法允許你對 AtomicBoolean 的目前值與一個期望值進行比較,如果目前值等于期望值的話,将會對 AtomicBoolean 設定一個新值。compareAndSet() 方法是原子性的,是以在同一時間之内有單個線程執行它。是以 compareAndSet() 方法可被用于一些類似于鎖的同步的簡單實作。

以下是一個 compareAndSet() 示例:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

boolean expectedValue = true;  
boolean newValue      = false;  

boolean wasNewValueSet = atomicBoolean.compareAndSet(expectedValue, newValue);  
           

本示例對 AtomicBoolean 的目前值與 true 值進行比較,如果相等,将 AtomicBoolean 的值更新為 false。

4. 原子性整型 AtomicInteger

AtomicInteger 類為我們提供了一個可以進行原子性讀和寫操作的 int 變量,它還包含一系列先進的原子性操作,比如 compareAndSet()。AtomicInteger 類位于 java.util.concurrent.atomic 包,是以其完整類名為 java.util.concurrent.atomic.AtomicInteger。本小節描述的 AtomicInteger 是 Java 8 版本裡的,而不是它第一次被引入的 Java 5 版本。

AtomicInteger 背後的設計理念在我的《Java 并發指南》主題的《比較和交換》小節有解釋。

建立一個 AtomicInteger

建立一個 AtomicInteger 示例如下:

AtomicInteger atomicInteger = new AtomicInteger();  
           

本示例将建立一個初始值為 0 的 AtomicInteger。

如果你想要建立一個給定初始值的 AtomicInteger,你可以這樣:

AtomicInteger atomicInteger = new AtomicInteger();  
           

本示例将 123 作為參數傳給 AtomicInteger 的構造子,它将設定 AtomicInteger 執行個體的初始值為 123。

擷取 AtomicInteger 的值

你可以使用 get() 方法擷取 AtomicInteger 執行個體的值。示例如下:

AtomicInteger atomicInteger = new AtomicInteger();  
int theValue = atomicInteger.get();  
           

設定 AtomicInteger 的值

你可以通過 set() 方法對 AtomicInteger 的值進行重新設定。以下是 AtomicInteger.set() 示例:

AtomicInteger atomicInteger = new AtomicInteger();  
atomicInteger.set();  
           

以上示例建立了一個初始值為 123 的 AtomicInteger,而在第二行将其值更新為 234。

比較并設定 AtomicInteger 的值

AtomicInteger 類也通過了一個原子性的 compareAndSet() 方法。這一方法将 AtomicInteger 執行個體的目前值與期望值進行比較,如果二者相等,為 AtomicInteger 執行個體設定一個新值。AtomicInteger.compareAndSet() 代碼示例:

AtomicInteger atomicInteger = new AtomicInteger();  

int expectedValue = ;  
int newValue      = ;  
atomicInteger.compareAndSet(expectedValue, newValue);  
           

本示例首先建立一個初始值為 123 的 AtomicInteger 執行個體。然後将 AtomicInteger 與期望值 123 進行比較,如果相等,将 AtomicInteger 的值更新為 234。

增加 AtomicInteger 值

AtomicInteger 類包含有一些方法,通過它們你可以增加 AtomicInteger 的值,并擷取其值。這些方法如下:

  • addAndGet()
  • getAndAdd()
  • getAndIncrement()
  • incrementAndGet()

第一個 addAndGet() 方法給 AtomicInteger 增加了一個值,然後傳回增加後的值。getAndAdd() 方法為 AtomicInteger 增加了一個值,但傳回的是增加以前的 AtomicInteger 的值。具體使用哪一個取決于你的應用場景。以下是這兩種方法的示例:

AtomicInteger atomicInteger = new AtomicInteger();  
System.out.println(atomicInteger.getAndAdd());  
System.out.println(atomicInteger.addAndGet());  
           

本示例将列印出 0 和 20。例子中,第二行拿到的是加 10 之前的 AtomicInteger 的值。加 10 之前的值是 0。第三行将 AtomicInteger 的值再加 10,并傳回加操作之後的值。該值現在是為 20。

你當然也可以使用這倆方法為 AtomicInteger 添加負值。結果實際是一個減法操作。

getAndIncrement() 和 incrementAndGet() 方法類似于 getAndAdd() 和 addAndGet(),但每次隻将 AtomicInteger 的值加 1。

減小 AtomicInteger 的值

AtomicInteger 類還提供了一些減小 AtomicInteger 的值的原子性方法。這些方法是:

  • decrementAndGet()
  • getAndDecrement()

decrementAndGet() 将 AtomicInteger 的值減一,并傳回減一後的值。getAndDecrement() 也将 AtomicInteger 的值減一,但它傳回的是減一之前的值。

5. 原子性長整型 AtomicLong

AtomicLong 類為我們提供了一個可以進行原子性讀和寫操作的 long 變量,它還包含一系列先進的原子性操作,比如 compareAndSet()AtomicLong 類位于 java.util.concurrent.atomic 包,是以其完整類名為 java.util.concurrent.atomic.AtomicLong。本小節描述的 AtomicLong 是 Java 8 版本裡的,而不是它第一次被引入的 Java 5 版本。

AtomicLong 背後的設計理念在我的《Java 并發指南》主題的《比較和交換》小節有解釋。

建立一個 AtomicLong

建立一個 AtomicLong 如下:

AtomicLong atomicLong = new AtomicLong();  
           

将建立一個初始值為 0 的 AtomicLong。

如果你想建立一個指定初始值的 AtomicLong,可以:

AtomicLong atomicLong = new AtomicLong();  
           

本示例将 123 作為參數傳遞給 AtomicLong 的構造子,後者将 AtomicLong 執行個體的初始值設定為 123。

擷取 AtomicLong 的值

你可以通過 get() 方法擷取 AtomicLong 的值。AtomicLong.get() 示例:

AtomicLong atomicLong = new AtomicLong();  

long theValue = atomicLong.get();  
           

設定 AtomicLong 的值

你可以通過 set() 方法設定 AtomicLong 執行個體的值。一個 AtomicLong.set() 的示例:

AtomicLong atomicLong = new AtomicLong();  

atomicLong.set();  
           

本示例建立了一個初始值為 123 的 AtomicLong,第二行将其值設定為 234。

比較并設定 AtomicLong 的值

AtomicLong 類也有一個原子性的 compareAndSet() 方法。這一方法将 AtomicLong 執行個體的目前值與一個期望值進行比較,如果兩種相等,為 AtomicLong 執行個體設定一個新值。AtomicLong.compareAndSet() 使用示例:

AtomicLong atomicLong = new AtomicLong();  

long expectedValue = ;  
long newValue      = ;  
atomicLong.compareAndSet(expectedValue, newValue);  
           

本示例建立了一個初始值為 123 的 AtomicLong。然後将 AtomicLong 的目前值與期望值 123 進行比較,如果相等的話,AtomicLong 的新值将變為 234。

增加 AtomicLong 值

AtomicLong 具備一些能夠增加 AtomicLong 的值并傳回自身值的方法。這些方法如下:

  • addAndGet()
  • getAndAdd()
  • getAndIncrement()
  • incrementAndGet()

第一個方法 addAndGet() 将 AtomicLong 的值加一個數字,并傳回增加後的值。第二個方法 getAndAdd() 也将 AtomicLong 的值加一個數字,但傳回的是增加前的 AtomicLong 的值。具體使用哪一個取決于你自己的場景。示例如下:

AtomicLong atomicLong = new AtomicLong();  
System.out.println(atomicLong.getAndAdd());  
System.out.println(atomicLong.addAndGet());  
           

本示例将列印出 0 和 20。例子中,第二行拿到的是加 10 之前的 AtomicLong 的值。加 10 之前的值是 0。第三行将 AtomicLong 的值再加 10,并傳回加操作之後的值。該值現在是為 20。

你當然也可以使用這倆方法為 AtomicLong 添加負值。結果實際是一個減法操作。

getAndIncrement() 和 incrementAndGet() 方法類似于 getAndAdd() 和 addAndGet(),但每次隻将 AtomicLong 的值加 1。

減小 AtomicLong 的值

AtomicLong 類還提供了一些減小 AtomicLong 的值的原子性方法。這些方法是:

  • decrementAndGet()
  • getAndDecrement()

decrementAndGet() 将 AtomicLong 的值減一,并傳回減一後的值。getAndDecrement() 也将 AtomicLong 的值減一,但它傳回的是減一之前的值。

6. 原子性引用型 AtomicReference

AtomicReference 提供了一個可以被原子性讀和寫的對象引用變量。原子性的意思是多個想要改變同一個 AtomicReference 的線程不會導緻 AtomicReference 處于不一緻的狀态。AtomicReference 還有一個 compareAndSet() 方法,通過它你可以将目前引用于一個期望值(引用)進行比較,如果相等,在該 AtomicReference 對象内部設定一個新的引用。

建立一個 AtomicReference

建立 AtomicReference 如下:

AtomicReference atomicReference = new AtomicReference();  
           

如果你需要使用一個指定引用建立 AtomicReference,可以:

String initialReference = "the initially referenced string";  
AtomicReference atomicReference = new AtomicReference(initialReference);  
           

建立泛型 AtomicReference

你可以使用 Java 泛型來建立一個泛型 AtomicReference。示例:

AtomicReference<String> atomicStringReference =  
    new AtomicReference<String>();  
           

你也可以為泛型 AtomicReference 設定一個初始值。示例:

String initialReference = "the initially referenced string";  
AtomicReference<String> atomicStringReference =  new AtomicReference<String>(initialReference);  
           

擷取 AtomicReference 引用

你可以通過 AtomicReference 的 get() 方法來擷取儲存在 AtomicReference 裡的引用。如果你的 AtomicReference 是非泛型的,get() 方法将傳回一個 Object 類型的引用。如果是泛型化的,get() 将傳回你建立 AtomicReference 時聲明的那個類型。

先來看一個非泛型的 AtomicReference get() 示例:

AtomicReference atomicReference = new AtomicReference("first value referenced");  
String reference = (String) atomicReference.get();  
           

注意如何對 get() 方法傳回的引用強制轉換為 String。

泛型化的 AtomicReference 示例:

AtomicReference<String> atomicReference =   new AtomicReference<String>("first value referenced");
String reference = atomicReference.get();  
           

編譯器知道了引用的類型,是以我們無需再對 get() 傳回的引用進行強制轉換了。

設定 AtomicReference 引用

你可以使用 get() 方法對 AtomicReference 裡邊儲存的引用進行設定。如果你定義的是一個非泛型 AtomicReference,set() 将會以一個 Object 引用作為參數。如果是泛型化的 AtomicReference,set() 方法将隻接受你定義給的類型。

AtomicReference set() 示例:

AtomicReference atomicReference = new AtomicReference();  
atomicReference.set("New object referenced");  
           

這個看起來非泛型和泛型化的沒啥差別。真正的差別在于編譯器将對你能夠設定給一個泛型化的 AtomicReference 參數類型進行限制。

比較并設定 AtomicReference 引用

AtomicReference 類具備了一個很有用的方法:compareAndSet()。compareAndSet() 可以将儲存在 AtomicReference 裡的引用于一個期望引用進行比較,如果兩個引用是一樣的(并非 equals() 的相等,而是 == 的一樣),将會給AtomicReference 執行個體設定一個新的引用。

如果 compareAndSet() 為 AtomicReference 設定了一個新的引用,compareAndSet() 将傳回 true。否則compareAndSet() 傳回 false。

AtomicReference compareAndSet() 示例:

String initialReference = "initial value referenced";  

AtomicReference<String> atomicStringReference =  
    new AtomicReference<String>(initialReference);  

String newReference = "new value referenced";  
boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference);  
System.out.println("exchanged: " + exchanged);  

exchanged = atomicStringReference.compareAndSet(initialReference, newReference);  
System.out.println("exchanged: " + exchanged);  
           

本示例建立了一個帶有一個初始引用的泛型化的 AtomicReference。之後兩次調用 comparesAndSet()來對存儲值和期望值進行對比,如果二者一緻,為 AtomicReference 設定一個新的引用。第一次比較,存儲的引用(initialReference)和期望的引用(initialReference)一緻,是以一個新的引用(newReference)被設定給 AtomicReference,compareAndSet() 方法傳回 true。第二次比較時,存儲的引用(newReference)和期望的引用(initialReference)不一緻,是以新的引用沒有被設定給 AtomicReference,compareAndSet() 方法傳回 false。