文章目錄
爲什麼需要它們?
public class Counter{
private int count;
public synchronized void incr(){
count ++;
}
public synchronzied int getCount(){
return count;
}
}
對於count++ 這種操作來說,使用synchronized 成本太高了,需要先獲取鎖,最後需要釋放鎖,獲取不到鎖的情況下需要等待,還會有線程的上下文切換,這些都是需要成本的。
對於以上這種情況,完全可以使用原子變量代替。
原子變量類型
AtomicBoolean : 原子Boolean 類型,常用來程序中表示一個標誌位。
AtomicInteger: 原子Integer 類型。
1.基本用法
// 構造方法
public AtomicInteger(int initialValue); // 給定一個初始值
public AtomicInteger(); // 初始值爲0
// set 和 get 方法
public final int get();
public final void set(int newValue);
2.原子方式實現組合操作的方法
// 以原子方式獲取舊值並設置新值
public final int getAndSet(int newValue);
// 以原子方式獲取舊值並給當前值加1
public final int getAndIncrement();
// 以原子方式獲取舊值並給當前值減1
public final int getAndDecrement();
// 以原子方式獲取舊值並給當前值加delta
public final int getAndAdd(int delta);
// 以原子方式給當前值加1並獲取新值
public final int incrementAndGet();
//以原子方式給當前值減1並獲取新值
public final int decrementAndGet();
// 以原子方式給當前值加delta並獲取新值
public final int addAndGet(int delta);
這些方法都依賴另一個public 方法:
public final boolean compareAndSet(int expect, int update)
compareAndSet 方法,比較並設置,我們簡稱爲CAS。 該方法有兩個參數expect 和 update,以原子方式實現瞭如下功能:如果當前值等於expect ,則更新爲update,否則不更新,如果更新成功,返回true,否則返回false。
3.AtomicInteger 可以在程序中用作一個計數器,多個線程併發更新,也總能實現正確性。
package com.claa.javabasic.Thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author: claa
* @Date: 2020/04/18 22:33
* @Description: AtomicInteger 實例
*/
public class AtomicIntegerDemo {
private static AtomicInteger counter = new AtomicInteger(0);
static class Visitor extends Thread {
@Override
public void run() {
for(int i = 0; i < 1000; i++){
counter.incrementAndGet();
}
}
}
public static void main(String[] args) throws InterruptedException {
int num = 1000;
Thread[] threads = new Thread[num];
for(int i =0; i < num; i++){
threads[i] = new Visitor();
threads[i].start();
}
for(int i = 0; i < num; i++ ){
threads[i].join();
}
System.out.println(counter.get());
}
}
4.基本實現方式
// 主要內部成員,聲明帶有volatile ,這是必須的,以保證內存的可見性
private volatile int value;
// incrementAndGet方法具體代碼
public final int incrementAndGet(){
for(;;){
int current = get();
int next = current +1;
if(compareAndSet(current,next))
return next;
}
}
代碼主體是一個死循環,先獲取當前值current,計算期望的值next ,然後調用CAS方法進行更新,如果更新沒有成功,說明value 被別的線程改了,則再去取最新值並嘗試更新直到成功爲止。
AtomicLong: 原子Long類型,常用來在程序中生成唯一序列號。
AtomicReference: 原子引用類型,用來以原子方式更新複雜類型。
與synchronized 比較
synchronized 是悲觀的,它假定更新很可能衝突,所以先獲取鎖,得到鎖後才更新;
原子變量的更新邏輯是樂觀的,它假定衝突比較少,但使用CAS更新,也就是進行衝突檢測,如果確實衝突了,那也沒有關係,繼續嘗試就好了。
synchronized 代表一種阻塞式算法,得不到鎖的時候,進入鎖等待隊列,等待其他線程喚醒,有上下文切換開銷。
原子變量的更新是非阻塞式的,更新衝突的時候,它就重試,不會阻塞,不會有上下文切換開銷。對於大部分比較簡單的操作,無論是在低併發還是高併發情況下,這種樂觀非阻塞方式的性能都遠高於悲觀阻塞方式。
compareAndSet是如何實現的呢?
public final boolean compareAndSet(int expect, int update){
return unsafe.compareAndSwapInt(this,valueOffset,expect,update);
}
unsafe 是什麼?它的類型爲sun.misc.Unsafe
public static final Unsafe unsafe = Unsafe.getUnsafe();
它是Sun的私有實現,從名字看,表示的也是“不安全”,一般應用程序不應該直接使用,原理上,一般的計算機都在硬件層次上支持CAS 指令,而Java的實現都利用這些特殊的指令。
基於CAS 實現鎖
package com.claa.javabasic.Thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author: claa
* @Date: 2020/04/19 08:26
* @Description: 基於CAS 實現的鎖
*/
public class MyLock {
private AtomicInteger status = new AtomicInteger(0);
public void lock(){
while( ! status.compareAndSet(0,1)){
Thread.yield();
}
}
public void unlock(){
status.compareAndSet(1,0);
}
}
在 MyLock 中,使用status 表示鎖的狀態,0表示未鎖定,1表示鎖定,lock()、unlock() 使用CAS 方法更新,lock()只有在更新成功後才退出,實現了阻塞的效果,不過這種阻塞方式過於消耗CPU,實際開發中使用ReentrantLock
ABA 問題
該問題是指 ,假設當前值爲A,如果另一個線程先將A修改爲B,再修改回成A,當前線程的CAS 操作無法辨認當前值發生過變化。
解決方法是使用AtomicStampedReference,在修改值得同時附加一個時間戳,只有值和時間戳都相同才進行修改,其CAS方法聲明爲:
public boolean compareAndSet(
V expectedReference ,V newReference, int expectedStamp, int newStamp
)
Pair pair = new Pair(100,200);
int stamp = 1;
AtomicStampedReference<Pair> pairRef = new AtomicStampedReference<Pair>(pair,stamp);
int newStamp =2;
pairRef.compareAndSet(pair,new Pair(200,200),stamp,newStamp);
AtomicStampedReference 在 compareAndSet中要同時修改兩值:一個是引用,一個是時間戳。它怎麼實現原子性呢? 實際上,內部AtomicStampedReference會將兩個值組合成爲一個對象,修改的是一個值。
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp){
Pair<V> current = pair;
return expectedReference == current.reference && expectedStamp == current.stamp
&& ((newReference == current.reference && newStamp == current,stamp) || casPair(current,Pair.of(newReference,newStamp)) );
}
private static class Pair<T>{
final T reference;
final int stamp;
private Pair(T reference, int stamp){
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp){
return new Pair<T>(reference, stamp);
}
}
AtomicStampedReference 將引用值和時間戳的組合比較和修改轉換爲了對這個內部類Pair 單個值的比較和修改。