轉載自:
https://blog.csdn.net/timheath/article/details/71441008
收益匪淺,感謝!
我在詳解JUC之原子類概述這篇文章中介紹了一下原子操作和JUC包下的原子類的類型,現在我就來介紹一下這些原子類。
操作基本類型的原子類
操作基本類型的原子類有3個
- AtomicInteger:操作int類型
- AtomicLong:操作long類型
- AtomicBoolean:操作boolean類型
這些操作基本類型數據的原子類的使用是非常簡單的,你對基本類型數據的操作,在這些相應的類中可以找到一些相應的方法。比如說的i++
和++i
,就分別對應着getAndIncrement()
和incrementAndGet()
方法,還有像加法如i = i + 3
就對應着addAndGet(int delta)
方法。
這3個類的操作是類似的,只要你看得懂英文一眼就知道它們的方法是做什麼的。需要再次強調的是,這些方法都是原子操作,類似於同步方法,但是沒有使用synchronized
關鍵字,也沒有鎖什麼的,而是用CAS+volatile關鍵字實現原子操作的,關於這點,在後面我會介紹一下原子類的實現。如果還不知道原子操作是什麼,我建議你先去看詳解JUC之原子類概述,而且裏面有原子類解決多線程安全問題的例子,在這裏就不重複展示了。
下面我就簡單地演示一下操作基本類型的原子類的一些方法
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;
public class Demo {
@Test
public void testAtomicInteger() {
AtomicInteger i = new AtomicInteger(100);
int val = i.getAndIncrement();
System.out.println(val);// 100
System.out.println(i.intValue());// 101
val = i.addAndGet(9);
System.out.println(val);// 110
}
@Test
public void testAtomicLong() {
AtomicLong l = new AtomicLong(100);
long val = l.getAndIncrement();
System.out.println(val);// 100
System.out.println(l.intValue());// 101
val = l.addAndGet(9);
System.out.println(val);// 110
}
@Test
public void testAtomicBoolean(){
AtomicBoolean b = new AtomicBoolean(true);
boolean val = b.getAndSet(false);
System.out.println(val);//true
val = b.get();
System.out.println(val);//false
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
實現原理
大家觀察這些原子類,比如說AtomicInteger
,它的名字包含了一個Integer
,那爲什麼我還要說這是操作基本類型的呢,這明顯是包裝類呀。別急,點進AtomicInteger
的源碼看看你就知道,它裏面持有一個int
類型變量value
,這個你一定要記住,因爲它的所有操作都是圍繞着它的。
private volatile int value;
- 1
同理AtomicLong
和AtomicBoolean
也持有它們各自的對應的基本類型變量value
,不行你就去看它們的源代碼咯。還一點需要注意的是它們都被volatile
關鍵字修飾了。
在講解它們的實現前,我們先要了解CAS和volatile是什麼鬼,我就不在這詳細講這兩個東西了,不然篇幅又要老長了,關於CAS可以看Java中CAS詳解,關於volatile可以看java中volatile關鍵字的含義
鑑於它們3個類的實現差不多,所以我就以AtomicInteger
類來講解。
我就以int addAndGet(int delta)
方法開始,這個方法的意思就是加一個數並獲取結果,比如說value
是3而delta
是5則返回8,下面是這個方法的源碼
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
可以看到只有一行代碼,這其中有個很重要的東西就是unsafe
,這個是sun.misc.Unsafe
類型的實例,現在一定注意,這個Unsafe
類在JUC包中被頻繁使用到的,所以特別重要。
sun.misc.unsafe
這個類可以用來在任意內存地址位置處讀寫數據,另外,還支持一些CAS原子操作。可見,對於普通用戶來說,使用起來還是比較危險的,一般只是給開發Java API的用,所以人家將它保護地好好的呢,不耍點手段是用不着的了,如果你真想用可以參看sun.misc.Unsafe類的使用。
嚴格上來說Unsafe
類它不是Java SE中真正的一部份,所以你儘管去Java官方的JDK API文檔找,找得到算我輸。雖然在Oracle那我沒找到,不過我在DocJar: Search Open Source Java API找到了(記住這個網站,搜索Java API很方便)。
這裏就是它的API文檔http://www.docjar.com/docs/api/sun/misc/Unsafe.html,查看它裏面的一些方法,你就可以發現裏面有很多本地(native)方法。
到了這裏我覺得沒必要再深入專研下去了,人家都這麼保護這個Unsafe
類,我們還是別戳破它吧,知道它是幹嘛的就好了。
Java 7和Java 8的AtomicInteger源碼區別
再回到我們的AtomicInteger
源碼,爲了方便看我再把int addAndGet(int delta)
方法貼上來,可以看見這個方法中的唯一一句代碼是unsafe
調用了方法getAndAddInt
並將方法的返回值(先get再add說明返回的是value,就像i++先使用i值再進行i+1)加上delta
得到的值作爲最終返回結果,看方法名我們就很容易理解它到底幹了什麼。
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
相應的int getAndAdd(int delta)
你也應該能看懂,這個更簡單,直接調用unsafe
的getAndAddInt
方法,不就是先獲取value
值作爲返回結果,再對value
加delta
並將結果賦值回給value
嘛
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
經過我上面的介紹你就應該知道我們是無法Unsafe
的源碼的,所以它的方法源碼我們看不到。不過這樣子會不會覺得很沒意思,說好的源碼分析呢。行,下面就給你看看在Java 7的AtomicInteger
源碼,下面是addAndGet
方法
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
for (;;) {
//獲取value值賦值給current
int current = get();
//將current值(這個可能不會等於value值,因爲可能value值已經被其它線程修改)加上delta並賦值給next
int next = current + delta;
//比較並設置成功的話就返回結果next,不然又會再次循環
if (compareAndSet(current, next))
return next;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
吶吶吶,是不是多了幾行代碼,下面我就來解析一下這個方法
(1) 首先第一步獲取value
值,並賦值給current
,get
方法代碼如下,只是“簡單”地返回value
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
這個其實不簡單,還記得我說過value
被volatile
關鍵字修飾了嘛,說明此時get
方法獲取到的value
一定是最新的。
(2) 然後第二步是,將current
加上delta
並賦值給next
,這裏需要注意的是,此時的current
可能不會等於value
,因爲value
可能已經被其它線程修改。
(3)然後第三步就是最重要的一步,我們先來看一下compareAndSet
方法
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
可以看到這個方法用到了Unsafe
的compareAndSwapInt
函數,看名字就知道它是一個CAS函數了。
這個方法的作用就是,如果當前AtomicInteger
持有的value
值等於expect
,則將value
值設置成update
,並返回true,不然就什麼都不做返回false。
用大白話來說就是,誒,value
你被改過沒有呀,沒有的話我就把你變成update
。
再接下來,如果compareAndSet
方法返回的是true,說明value
並沒有被其它線程所修改,說此時操作的結果是正確的,即current
的值還是等於value
的,所以current + delta
的結果就是我們想要的,所以直接返回結果。如果是false話,二話不說開始下一次嘗試,別忘記for (;;)
喲。
至於爲什麼要用for (;;)
而不是while(true)
,你可以看看爲什麼JDK源碼中,無限循環大多使用for(;;)而不是while(true)?
關於自旋和樂觀策略
關於上面addAndGet
的實現,我們可以發現裏面有個無限循環,不停地去嘗試修改操作,直到修改成功,這就是自旋,是一種樂觀的策略。就是我假設每次執行都沒人跟我爭,有人跟我爭那我就再多嘗試一次。而悲觀的策略就是我認爲一定會有人跟我爭的,所以每次我要執行代碼的時候我先關上門(上鎖)不給別人進。
自旋操作呢有一個比較大的缺點就是,在併發程度高的情況下,可能一個線程嘗試了好久都沒成功,那你想咯,一個線程沒有放棄CPU執行權在那裏不斷循環,不是很浪費CPU資源嗎?
我在後續的文章還會介紹JUC中的鎖(Lock),裏面有一個概念叫自旋鎖。Java中的鎖有很多講究,很有特點,你可以看Java鎖的膨脹過程和優化 瞭解一下
總結操作基本類型的原子類
總結一下操作基本類型的原子類,它們的內部都維護者一個對應的基本類型的成員變量value
,這個變量是被volatile
關鍵字修飾的,這可以保證每次一個線程要使用它都會拿到最新的值。然後在進行一些原子操作的時候,需要依賴一個神祕人物Unsafe
類(這傢伙是一個隱藏很深的東西,我們只需要知道它是幹嘛的就好了),這個類裏面就有一些CAS函數,一些原子操作就是通過自旋方式,不斷地使用CAS函數進行嘗試直到達到自己的目的。
關於操作基本類型的原子類我就介紹到這裏,可能你會覺得我介紹的不全,不過我覺得是夠了,因爲無論是在方法使用方面還是實現方面我都介紹了一兩個典型的例子,而其它的也就是以此類推,都可以看懂的了,看不懂你找我。
其它原子類
至於操作其它類型的原子類,老實說,我本來想每種類型都寫篇文章介紹一下他們的使用和實現的,不過我感覺很沒必要,因爲它們都是換湯不換藥的,實現的套路都差不多,可能操作的類型不同所以功能不太同,但是看API的話你肯定可以知道它是幹嘛的,比如說操作整型數組的原子類java.util.concurrent.atomic.AtomicIntegerArray
有個方法int getAndSet(int i, int newValue)
方法,看名字就能猜到,先獲取索引爲i的元素再將索引i的元素設置爲newValue
,真的是沒什麼好講的。