問題:請闡述,什麼是線程的原子性?
原子性:線程在執行一個操作或者多個操作,一但開始執行,要麼執行成功,要麼執行失敗。在執行的過程中不允許被其他線程打斷(不會切換到其他線程)。看下面代碼
int a = 1;
原子操作(具有原子性),即使在併發環境下, 同一個時刻只有一個線程操作
count++;
非原子操作,這代碼實際分3步操作:
1> 內存中獲取count的值
2> 執行count+1操作
3> 加一結果賦值到count變量
不進行額外的設置,此時count++是非原子操作,併發環境下,同一時刻可以多個線程可以執行上面不同的步驟。
原子操作
實現原子操作方式最簡單方式使用synchronized同步塊或者lock機制
synchronized (this){
count++;
}
try {
lock.tryLock();
count++;
}finally {
lock.unlock();
}
此處我們介紹另外一中原子操作:Java中的Atomic包提供的原子操作
原子操作類:Atomic*
JDK5之後,java提供了提供一套專用於原子操作的api,根據類用的用途大體分為4大類:原子更新基本類型,原子更新數組,原子更新引用和原子更新字段。
基本類型:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
數組類型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
字段類型:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
引用類型:AtomicMarkableReference,AtomicStampedReference
此處我們僅僅討論基本類型以及其操作原理
基本類型
先上代碼:體驗下如何使用
需求:1000個線程對共享變量自增操作, 每一個線程+1
public class Resource implements Runnable {
public int i= 0; //普通成員變量自增
public volatile int j = 0; //使用volatile修飾變量自增
public AtomicInteger ai = new AtomicInteger(); //使用原子操作類自增
public AutoCounter ct = new AutoCounter(); //自定義類使用synchronized限制自增
//自增
public void run() {
try {
Thread.sleep(10); //讓效果更明顯
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
j++;
ai.incrementAndGet();
ct.count();
}
}
public class AutoCounter {
private int count = 0;
//自增加一
public synchronized void count(){
count++;
}
//獲取count
public synchronized int getCount(){
return this.count;
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
Resource resource = new Resource();
//啟動1000線程自增1
for (int i = 0; i < 1000; i++){
new Thread(resource).start();
}
Thread.sleep(3000);
System.out.println("普通成員變量i:" + resource.i);
System.out.println("volatile變量j:" + resource.j);
System.out.println("原子操作類變量:" + resource.ai.get());
System.out.println("synchronized變量:" + resource.ct.getCount());
}
}
結果:
普通成員變量i:989
volatile變量j:995
原子操作類變量:1000
synchronized變量:1000
普通成員變量i跟volatile修飾的變量都無法自增到1000, 說明2點:
1>成員變量線程不安全,
2>volatile操作做無法做到原子操作
而原子操作的類與synchronized修飾自定義類可以完成, 那麼我們不經要想, 原子操作的類是怎麼做到同步的呢?要解釋這個,就得從CAS說起
CAS原理
CAS(compare and swap),用於解決多線程並行情況下使用鎖造成性能損耗的一種機制,CAS操作包含三個操作數——內存原值(V)、期望值(A)和新值(B)。如果V值等於A值,那麼處理器會自動將V值更新為B值。否則,不做任何操作。
明白概念之後,我們看回AtomicInteger 類:
持有3個屬性:
value:CAS中的內存原值(V), 在代碼層面的顯示值
Unsafe : unsafe類一個很特別的類,名字叫“不安全”,可以直接讀寫內存、獲得地址偏移值、鎖定或釋放線程。
valueOffset:CAS中內存原值(V)的內存地址(偏移量), Unsafe類通過valueOffset值可以直接操作內存(硬件的寄存器內存)
靜態代碼塊:
AtomicInteger 加載進內存之後,直接獲取value值在內存的存儲位置
自增方法:
incrementAndGet: 委託unsafe對象實現,返回安全的CAS期待值(A), 然後自加1並返回。
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value; //cas中內存位置值-V值
//比較特殊的一個類,直接操作內存
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; //cas中V值的內存地址(偏移量)
static {
try {
//獲取對象某個屬性的地址偏移值。
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//自增長並返回,由unsafe對象方法實現
public final int incrementAndGet() {
//獲取到cas中的內存位置V值, 同時確保正確,然後加一返回
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
AtomicInteger 類的incrementAndGet方法委託Unsafe 進行CAS判斷。
Unsafe 定義3個方法實現:
objectFieldOffset:本地方法,直接獲取屬性對象的內存地址
getAndAddInt:獲取通過CAS判斷的期望值
compareAndSwapInt:本地方法, 進行CAS判斷
public final class Unsafe {
//本地方法,可以認為直接操作內存地址,返回指定屬性對象的值
public native long objectFieldOffset(Field var1);
//參數1:原子操作類對象(AtomicInteger )
//參數2:CAS中的內存偏移地址(AtomicInteger value屬性內存地址)
//參數3:自增步長
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5; //CAS中內存值(V)
do {
//通過原子操作類對象與value 屬性內存地址獲取cas中內存值(V)
var5 = this.getIntVolatile(var1, var2);
//循環操作,一直到CAS中的內存原值(V)與期望值一致,才進入下一步操作
//這裡可以理解為第一獲取的內存中原值var5與下一次獲取原值var5一致,
//表示沒有其他線程修改內存中的原值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//進行CAS判斷
//參數1:原子操作類對象(AtomicInteger )
//參數2:CAS中的內存偏移地址(AtomicInteger value屬性內存地址)
//參數3:CAS中的期望值(A)
//參數4:CAS中的更新值(B)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
}
參照上面的源碼, 發現沒有任何的同步限制,那又怎麼就實現了同步操作呢?其實,在代碼層面上雖然沒做同步限制,但在底層操作中以及包含同步操作的意思在裡面了:
底層不同的處理對CAS處理不一樣,在X86平臺,CPU給部分指令執行加鎖操作,實現指令的原子操作,上文提到CAS就是利用CMPXCHG指令實現的,而CMPXCHG指令是原子操作指令,在同一時刻,僅讓一個線程執行,那麼CAS結果只有2種結果,要麼成功,要麼失敗。如果對於CAS操作執行失敗的線程,做循環執行CAS操作,那麼一定能夠成功。那麼就有阻塞概念了。也可實現了同步機制。
到這,本篇就結束了, 有興趣的朋友可以繼續研究其他Atomic類。
閱讀更多 叩丁狼教育stef 的文章