并發(fā)編程之ThreadLocal、Volatile、synchronized、Atomic關(guān)鍵字掃盲

前言
對(duì)于ThreadLocal、Volatile、synchronized、Atomic這四個(gè)關(guān)鍵字,我想一提及到大家肯定都想到的是解決在多線程并發(fā)環(huán)境下資源的共享問(wèn)題,但是要細(xì)說(shuō)每一個(gè)的特點(diǎn)、區(qū)別、應(yīng)用場(chǎng)景、內(nèi)部實(shí)現(xiàn)等,卻可能模糊不清,說(shuō)不出個(gè)所以然來(lái),所以,本文就對(duì)這幾個(gè)關(guān)鍵字做一些作用、特點(diǎn)、實(shí)現(xiàn)上的講解。
1、Atomic
作用
對(duì)于原子操作類,Java的concurrent并發(fā)包中主要為我們提供了這么幾個(gè)常用的:AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference。
對(duì)于原子操作類,最大的特點(diǎn)是在多線程并發(fā)操作同一個(gè)資源的情況下,使用Lock-Free算法來(lái)替代鎖,這樣開(kāi)銷(xiāo)小、速度快,對(duì)于原子操作類是采用原子操作指令實(shí)現(xiàn)的,從而可以保證操作的原子性。什么是原子性?比如一個(gè)操作i++;實(shí)際上這是三個(gè)原子操作,先把i的值讀取、然后修改(+1)、最后寫(xiě)入給i。所以使用Atomic原子類操作數(shù),比如:i++;那么它會(huì)在這步操作都完成情況下才允許其它線程再對(duì)它進(jìn)行操作,而這個(gè)實(shí)現(xiàn)則是通過(guò)Lock-Free+原子操作指令來(lái)確定的
如:
AtomicInteger類中:
public?final int?incrementAndGet() {
????????for?(;;) {
????????????int?current = get();
????????????int?next = current + 1;
????????????if?(compareAndSet(current, next))
????????????????return?next;
????????}
????}而關(guān)于Lock-Free算法,則是一種新的策略替代鎖來(lái)保證資源在并發(fā)時(shí)的完整性的,Lock-Free的實(shí)現(xiàn)有三步:
1、循環(huán)(for(;;)、while)
2、CAS(CompareAndSet)
3、回退(return、break)
用法
比如在多個(gè)線程操作一個(gè)count變量的情況下,則可以把count定義為AtomicInteger,如下:
public?class?Counter?{
????private?AtomicInteger count = new?AtomicInteger();
????public?int?getCount() {
????????return?count.get();
????}
????public?void?increment() {
????????count.incrementAndGet();
????}
}在每個(gè)線程中通過(guò)increment()來(lái)對(duì)count進(jìn)行計(jì)數(shù)增加的操作,或者其它一些操作。這樣每個(gè)線程訪問(wèn)到的將是安全、完整的count。
內(nèi)部實(shí)現(xiàn)
采用Lock-Free算法替代鎖+原子操作指令實(shí)現(xiàn)并發(fā)情況下資源的安全、完整、一致性
2、Volatile
作用
Volatile可以看做是一個(gè)輕量級(jí)的synchronized,它可以在多線程并發(fā)的情況下保證變量的“可見(jiàn)性”,什么是可見(jiàn)性?就是在一個(gè)線程的工作內(nèi)存中修改了該變量的值,該變量的值立即能回顯到主內(nèi)存中,從而保證所有的線程看到這個(gè)變量的值是一致的。所以在處理同步問(wèn)題上它大顯作用,而且它的開(kāi)銷(xiāo)比synchronized小、使用成本更低。
舉個(gè)栗子:在寫(xiě)單例模式中,除了用靜態(tài)內(nèi)部類外,還有一種寫(xiě)法也非常受歡迎,就是Volatile+DCL:
public?class?Singleton?{
????private?static?volatile?Singleton instance;
????private?Singleton()?{
????}
????public?static?Singleton getInstance()?{
????????if?(instance == null) {
????????????synchronized?(Singleton.class) {
????????????????if?(instance == null) {
????????????????????instance = new?Singleton();
????????????????}
????????????}
????????}
????????return?instance;
????}
}這樣單例不管在哪個(gè)線程中創(chuàng)建的,所有線程都是共享這個(gè)單例的。
雖說(shuō)這個(gè)Volatile關(guān)鍵字可以解決多線程環(huán)境下的同步問(wèn)題,不過(guò)這也是相對(duì)的,因?yàn)樗痪哂胁僮鞯脑有裕簿褪撬贿m合在對(duì)該變量的寫(xiě)操作依賴于變量本身自己。舉個(gè)最簡(jiǎn)單的栗子:在進(jìn)行計(jì)數(shù)操作時(shí)count++,實(shí)際是count=count+1;,count最終的值依賴于它本身的值。所以使用volatile修飾的變量在進(jìn)行這么一系列的操作的時(shí)候,就有并發(fā)的問(wèn)題
舉個(gè)栗子:因?yàn)樗痪哂胁僮鞯脑有裕锌赡?號(hào)線程在即將進(jìn)行寫(xiě)操作時(shí)count值為4;而2號(hào)線程就恰好獲取了寫(xiě)操作之前的值4,所以1號(hào)線程在完成它的寫(xiě)操作后count值就為5了,而在2號(hào)線程中count的值還為4,即使2號(hào)線程已經(jīng)完成了寫(xiě)操作count還是為5,而我們期望的是count最終為6,所以這樣就有并發(fā)的問(wèn)題。而如果count換成這樣:count=num+1;假設(shè)num是同步的,那么這樣count就沒(méi)有并發(fā)的問(wèn)題的,只要最終的值不依賴自己本身。
用法
因?yàn)関olatile不具有操作的原子性,所以如果用volatile修飾的變量在進(jìn)行依賴于它自身的操作時(shí),就有并發(fā)問(wèn)題,如:count,像下面這樣寫(xiě)在并發(fā)環(huán)境中是達(dá)不到任何效果的:
public?class?Counter?{
????private?volatile?int?count;
????public?int?getCount(){
????????return?count;
????}
????public?void?increment(){
????????count++;
????}
}而要想count能在并發(fā)環(huán)境中保持?jǐn)?shù)據(jù)的一致性,則可以在increment()中加synchronized同步鎖修飾,改進(jìn)后的為:
public?class?Counter?{
????private?volatile?int?count;
????public?int?getCount(){
????????return?count;
????}
????public?synchronized?void?increment(){
????????count++;
????}
}3、synchronized
作用
synchronized叫做同步鎖,是Lock的一個(gè)簡(jiǎn)化版本,由于是簡(jiǎn)化版本,那么性能肯定是不如Lock的,不過(guò)它操作起來(lái)方便,只需要在一個(gè)方法或把需要同步的代碼塊包裝在它內(nèi)部,那么這段代碼就是同步的了,所有線程對(duì)這塊區(qū)域的代碼訪問(wèn)必須先持有鎖才能進(jìn)入,否則則攔截在外面等待正在持有鎖的線程處理完畢再獲取鎖進(jìn)入,正因?yàn)樗谶@種阻塞的策略,所以它的性能不太好,但是由于操作上的優(yōu)勢(shì),只需要簡(jiǎn)單的聲明一下即可,而且被它聲明的代碼塊也是具有操作的原子性。
用法
public?synchronized?void?increment(){
????????????count++;
????}
????public?void?increment(){
????????synchronized?(Counte.class){
????????????count++;
????????}
????}內(nèi)部實(shí)現(xiàn)
重入鎖ReentrantLock+一個(gè)Condition,所以說(shuō)是Lock的簡(jiǎn)化版本,因?yàn)橐粋€(gè)Lock往往可以對(duì)應(yīng)多個(gè)Condition
4、ThreadLocal
作用
關(guān)于ThreadLocal,這個(gè)類的出現(xiàn)并不是用來(lái)解決在多線程并發(fā)環(huán)境下資源的共享問(wèn)題的,它和其它三個(gè)關(guān)鍵字不一樣,其它三個(gè)關(guān)鍵字都是從線程外來(lái)保證變量的一致性,這樣使得多個(gè)線程訪問(wèn)的變量具有一致性,可以更好的體現(xiàn)出資源的共享。
而ThreadLocal的設(shè)計(jì),并不是解決資源共享的問(wèn)題,而是用來(lái)提供線程內(nèi)的局部變量,這樣每個(gè)線程都自己管理自己的局部變量,別的線程操作的數(shù)據(jù)不會(huì)對(duì)我產(chǎn)生影響,互不影響,所以不存在解決資源共享這么一說(shuō),如果是解決資源共享,那么其它線程操作的結(jié)果必然我需要獲取到,而ThreadLocal則是自己管理自己的,相當(dāng)于封裝在Thread內(nèi)部了,供線程自己管理。
用法
一般使用ThreadLocal,官方建議我們定義為private static ,至于為什么要定義成靜態(tài)的,這和內(nèi)存泄露有關(guān),后面再講。
它有三個(gè)暴露的方法,set、get、remove。
public?class?ThreadLocalDemo?{
????private?static?ThreadLocal threadLocal = new?ThreadLocal(){
????????@Override
????????protected?String initialValue()?{
????????????return?"hello";
????????}
????};
????static?class?MyRunnable?implements?Runnable{
????????private?int?num;
????????public?MyRunnable(int?num){
????????????this.num = num;
????????}
????????@Override
????????public?void?run()?{
????????????threadLocal.set(String.valueOf(num));
????????????System.out.println("threadLocalValue:"+threadLocal.get());
????????}
????}
????public?static?void?main(String[] args){
????????new?Thread(new?MyRunnable(1));
????????new?Thread(new?MyRunnable(2));
????????new?Thread(new?MyRunnable(3));
????}
} 運(yùn)行結(jié)果如下,這些ThreadLocal變量屬于線程內(nèi)部管理的,互不影響:
threadLocalValue:2
threadLocalValue:3
threadLocalValue:4對(duì)于get方法,在ThreadLocal沒(méi)有set值得情況下,默認(rèn)返回null,所有如果要有一個(gè)初始值我們可以重寫(xiě)initialValue()方法,在沒(méi)有set值得情況下調(diào)用get則返回初始值。
值得注意的一點(diǎn):ThreadLocal在線程使用完畢后,我們應(yīng)該手動(dòng)調(diào)用remove方法,移除它內(nèi)部的值,這樣可以防止內(nèi)存泄露,當(dāng)然還有設(shè)為static。
內(nèi)部實(shí)現(xiàn)
ThreadLocal內(nèi)部有一個(gè)靜態(tài)類ThreadLocalMap,使用到ThreadLocal的線程會(huì)與ThreadLocalMap綁定,維護(hù)著這個(gè)Map對(duì)象,而這個(gè)ThreadLocalMap的作用是映射當(dāng)前ThreadLocal對(duì)應(yīng)的值,它key為當(dāng)前ThreadLocal的弱引用:WeakReference
內(nèi)存泄露問(wèn)題
對(duì)于ThreadLocal,一直涉及到內(nèi)存的泄露問(wèn)題,即當(dāng)該線程不需要再操作某個(gè)ThreadLocal內(nèi)的值時(shí),應(yīng)該手動(dòng)的remove掉,為什么呢?我們來(lái)看看ThreadLocal與Thread的聯(lián)系圖:
此圖來(lái)自網(wǎng)絡(luò):

其中虛線表示弱引用,從該圖可以看出,一個(gè)Thread維持著一個(gè)ThreadLocalMap對(duì)象,而該Map對(duì)象的key又由提供該value的ThreadLocal對(duì)象弱引用提供,所以這就有這種情況:
如果ThreadLocal不設(shè)為static的,由于Thread的生命周期不可預(yù)知,這就導(dǎo)致了當(dāng)系統(tǒng)gc時(shí)將會(huì)回收它,而ThreadLocal對(duì)象被回收了,此時(shí)它對(duì)應(yīng)key必定為null,這就導(dǎo)致了該key對(duì)應(yīng)得value拿不出來(lái)了,而value之前被Thread所引用,所以就存在key為null、value存在強(qiáng)引用導(dǎo)致這個(gè)Entry回收不了,從而導(dǎo)致內(nèi)存泄露。
所以避免內(nèi)存泄露的方法,是對(duì)于ThreadLocal要設(shè)為static靜態(tài)的,除了這個(gè),還必須在線程不使用它的值是手動(dòng)remove掉該ThreadLocal的值,這樣Entry就能夠在系統(tǒng)gc的時(shí)候正常回收,而關(guān)于ThreadLocalMap的回收,會(huì)在當(dāng)前Thread銷(xiāo)毀之后進(jìn)行回收。
總結(jié)
關(guān)于Volatile關(guān)鍵字具有可見(jiàn)性,但不具有操作的原子性,而synchronized比volatile對(duì)資源的消耗稍微大點(diǎn),但可以保證變量操作的原子性,保證變量的一致性,最佳實(shí)踐則是二者結(jié)合一起使用。
1、對(duì)于synchronized的出現(xiàn),是解決多線程資源共享的問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式:訪問(wèn)串行化,對(duì)象共享化。同步機(jī)制是提供一份變量,讓所有線程都可以訪問(wèn)。
2、對(duì)于Atomic的出現(xiàn),是通過(guò)原子操作指令+Lock-Free完成,從而實(shí)現(xiàn)非阻塞式的并發(fā)問(wèn)題。
3、對(duì)于Volatile,為多線程資源共享問(wèn)題解決了部分需求,在非依賴自身的操作的情況下,對(duì)變量的改變將對(duì)任何線程可見(jiàn)。
4、對(duì)于ThreadLocal的出現(xiàn),并不是解決多線程資源共享的問(wèn)題,而是用來(lái)提供線程內(nèi)的局部變量,省去參數(shù)傳遞這個(gè)不必要的麻煩,ThreadLocal采用了“以空間換時(shí)間”的方式:訪問(wèn)并行化,對(duì)象獨(dú)享化。ThreadLocal是為每一個(gè)線程都提供了一份獨(dú)有的變量,各個(gè)線程互不影響。?
作者:Sunzxyong
出處:blog.csdn.net/u010687392/article/details/50549236
