一骤公、引言
前幾天面試,被大師虐殘了蚂会,好多基礎(chǔ)知識必須得重新拿起來啊淋样。閑話不多說,進入正題胁住。
二趁猴、為什么要線程同步
因為當我們有多個線程要同時訪問一個變量或?qū)ο髸r刊咳,如果這些線程中既有讀又有寫操作時,就會導致變量值或?qū)ο蟮臓顟B(tài)出現(xiàn)混亂儡司,從而導致程序異常椭符。舉個例子徙融,如果一個銀行賬戶同時被兩個線程操作毡熏,一個取100塊砖茸,一個存錢100塊。假設(shè)賬戶原本有0塊碉碉,如果取錢線程和存錢線程同時發(fā)生柴钻,會出現(xiàn)什么結(jié)果呢?取錢不成功垢粮,賬戶余額是100.取錢成功了贴届,賬戶余額是0.那到底是哪個呢?很難說清楚蜡吧。因此多線程同步就是要解決這個問題毫蚓。如果你想學習Java可以來這個群,首先是一二六昔善,中間是五三四元潘,最后是五一九君仆,里面有大量的學習資料可以下載氮帐。
三、不同步時的代碼
Bank.java
package threadTest;
SyncThreadTest.java
package threadTest;
代碼很簡單楞艾,我就不解釋了龄广,看看運行結(jié)果怎樣呢?截取了其中的一部分两入,是不是很亂,有寫看不懂裹纳。
余額不足
四剃氧、使用同步時的代碼
(1)同步方法:
即有synchronized關(guān)鍵字修飾的方法朋鞍。 由于java的每個對象都有一個內(nèi)置鎖滥酥,當用此關(guān)鍵字修飾方法時疏哗,內(nèi)置鎖會保護整個方法。在調(diào)用該方法前禾怠,需要獲得內(nèi)置鎖吗氏,否則就處于阻塞狀態(tài)污尉。
修改后的Bank.java
package threadTest;
再看看運行結(jié)果:
余額不足
瞬間感覺可以理解了吧仿村。
注: synchronized關(guān)鍵字也可以修飾靜態(tài)方法,此時如果調(diào)用該靜態(tài)方法焚志,將會鎖住整個類
(2)同步代碼塊
即有synchronized關(guān)鍵字修飾的語句塊酱酬。被該關(guān)鍵字修飾的語句塊會自動被加上內(nèi)置鎖,從而實現(xiàn)同步
Bank.java代碼如下:
package threadTest;
運行結(jié)果如下:
余額不足
效果和方法一差不多陨界。
注:同步是一種高開銷的操作滔灶,因此應(yīng)該盡量減少同步的內(nèi)容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關(guān)鍵代碼即可免钻。
(3)使用特殊域變量(Volatile)實現(xiàn)線程同步
a.volatile關(guān)鍵字為域變量的訪問提供了一種免鎖機制
b.使用volatile修飾域相當于告訴虛擬機該域可能會被其他線程更新
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作拆魏,它也不能用來修飾final類型的變量
Bank.java代碼如下:
package threadTest;
運行效果怎樣呢盯桦?
余額不足
是不是又看不懂了,又亂了渤刃。這是為什么呢拥峦?就是因為volatile不能保證原子操作導致的,因此volatile不能代替synchronized卖子。此外volatile會組織編譯器對代碼優(yōu)化略号,因此能不使用它就不適用它吧。它的原理是每次要線程要訪問volatile修飾的變量時都是從內(nèi)存中讀取揪胃,而不是存緩存當中讀取璃哟,因此每個線程訪問到的變量值都是一樣的。這樣就保證了同步喊递。如果你想學習Java可以來這個群,首先是一二六阳似,中間是五三四骚勘,最后是五一九,里面有大量的學習資料可以下載。
(4)使用重入鎖實現(xiàn)線程同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步俏讹。ReentrantLock類是可重入当宴、互斥、實現(xiàn)了Lock接口的鎖泽疆, 它與使用synchronized方法和快具有相同的基本行為和語義户矢,并且擴展了其能力。
ReenreantLock類的常用方法有:
ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖
注:ReentrantLock()還有一個可以創(chuàng)建公平鎖的構(gòu)造方法殉疼,但由于能大幅度降低程序運行效率梯浪,不推薦使用
Bank.java代碼修改如下:
package threadTest;
運行效果怎么樣呢?
余額不足
效果和前兩種方法差不多瓢娜。
如果synchronized關(guān)鍵字能滿足用戶的需求挂洛,就用synchronized,因為它能簡化代碼 眠砾。如果需要更高級的功能虏劲,就用ReentrantLock類,此時要注意及時釋放鎖褒颈,否則會出現(xiàn)死鎖柒巫,通常在finally代碼釋放鎖
(5)使用局部變量實現(xiàn)線程同步
Bank.java代碼如下:
package threadTest;
運行效果:
余額不足
看了運行效果,一開始一頭霧水谷丸,怎么只讓存堡掏,不讓取啊淤井?看看ThreadLocal的原理:
如果使用ThreadLocal管理變量布疼,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立币狠,這樣每一個線程都可以隨意修改自己的變量副本游两,而不會對其他線程產(chǎn)生影響。現(xiàn)在明白了吧漩绵,原來每個線程運行的都是一個副本贱案,也就是說存錢和取錢是兩個賬戶,知識名字相同而已止吐。所以就會發(fā)生上面的效果宝踪。
ThreadLocal與同步機制
a.ThreadLocal與同步機制都是為了解決多線程中相同變量的訪問沖突問題
b.前者采用以”空間換時間”的方法,后者采用以”時間換空間”的方式
現(xiàn)在都明白了吧碍扔。各有優(yōu)劣瘩燥,各有適用場景。