Java面試精講:一飛老師帶你再看Java線程(原子性)

問題:請闡述,什麼是線程的原子性?

原子性:線程在執行一個操作或者多個操作,一但開始執行,要麼執行成功,要麼執行失敗。在執行的過程中不允許被其他線程打斷(不會切換到其他線程)。看下面代碼

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*

Java面試精講:一飛老師帶你再看Java線程(原子性)

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類。


分享到:


相關文章: