并發(fā)編程(三):深入分析synchronized

一慈省、synchronized簡介

Java提供了強制性的鎖機制:synchronized憔维,可用來給對象和方法或者代碼塊加鎖,當(dāng)它鎖定一個方法或者一個代碼塊的時候牧嫉,同一時刻最多只有一個線程執(zhí)行這段代碼剂跟。當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行酣藻。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊曹洽。然而,當(dāng)一個線程訪問object的一個加鎖代碼塊時辽剧,另一個線程仍然可以訪問該object中的非加鎖代碼塊送淆。

包括兩種用法:synchronized方法和 synchronized 塊。

二怕轿、synchronized的使用

2.1 synchronized方法

public static synchronized void inc() {  
    ....  
}  

synchronized方法控制多個線程對該方法內(nèi)的成員的并發(fā)訪問坊夫,我們將inc方法申明為synchronized,所以同一時間只有一個線程可以訪問inc方法。當(dāng)?shù)谝粋€玩家進(jìn)來后撤卢,會對inc方法加一把鎖不讓別的玩家訪問环凿,玩家二如果也想訪問inc方法只能排隊等候直到玩家一訪問結(jié)束釋放鎖后,效果如下圖放吩。 雖然它現(xiàn)在是線程安全的了智听,但是這種方法過于極端,它的性能非常差渡紫。因為有時候我們需要共享的只是方法內(nèi)的部分?jǐn)?shù)據(jù)到推,其它數(shù)據(jù)是可以自由訪問的,那么這個時候我們應(yīng)該在項目中使用synchronized塊惕澎。

image

2.2 synchronized代碼塊

synchronized代碼塊控制線程訪問的數(shù)據(jù)在synchronized(obj)或synchronized(this){}里面莉测,同一時間也只能有一個線程可以訪問,別的請求線程將被阻塞在 synchronized(obj){}外邊唧喉,這樣可以不影響別的線程訪問不需要共享的數(shù)據(jù)捣卤。比如:inc() 玩家等級小于30 忍抽,不滿足條件程序直接return不用讓線程也阻塞在synchronized代碼塊外邊。

public void inc(Object obj) {  
    if(obj == null)  
    {  
       return;  
    }  
    synchronized (obj) {  
        count++;  
    }  
} 
public void inc(int lvl) {  
       if(lvl < 30)//玩家等級小于30 返回  
    {  
        return;  
    }  
    synchronized (this) {  
        count++;  
    }  
}  

2.3 對synchronized(this)的理解

  • 當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時董朝,一個時間內(nèi)只能有一個線程得到執(zhí)行鸠项。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。

  • 然而子姜,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊祟绊。

  • 尤其關(guān)鍵的是,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時哥捕,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞牧抽。

  • 第三個例子同樣適用其它同步代碼塊,它就獲得了這個object的對象鎖遥赚。結(jié)果阎姥,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。

  • 以上規(guī)則對其它對象鎖同樣適用鸽捻。

    以上是摘自百度百科對synchronized的理解呼巴,詳細(xì)使用參考這篇文章:http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html

三、內(nèi)部鎖的重進(jìn)入

當(dāng)一個線程請求其它線程已經(jīng)占有的鎖時御蒲,請求線程將被阻塞衣赶。然而內(nèi)部鎖是可重進(jìn)入的,因此線程在試圖獲得它自己占有的鎖時厚满,請求會成功府瞄。重進(jìn)入意味著鎖的請求是基于“每線程”,而不是基于“每調(diào)用”的碘箍。重進(jìn)入的實現(xiàn)是通過每個鎖關(guān)聯(lián)一個請求計數(shù)和一個占有它的線程遵馆。當(dāng)計數(shù)為0時,認(rèn)為鎖時未被占有的丰榴。線程請求一個未被占有的鎖時货邓,JVM將記錄鎖的占有者,并且將請求計數(shù)置為1四濒。如果同一線程再次請求這個鎖换况,計數(shù)將遞增;每次占用線程退出同步塊盗蟆,計數(shù)器值將遞減戈二。直到計數(shù)器達(dá)到0時,鎖被釋放喳资【蹩裕【摘自JAVA并發(fā)編程實戰(zhàn)】

3.1代碼示例

package com.game.lll.syn;  
  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  
  
  
public class UnsafeCount {  
    public static int count = 0;  
    static LoggingWidget loggingWidget = new LoggingWidget();  
    public static void inc() {  
        loggingWidget.doSomething();  
    }  
  
    public static void main(String[] args) throws InterruptedException {  
  
        ExecutorService service=Executors.newFixedThreadPool(Integer.MAX_VALUE);  
  
        for (int i = 0; i < 10; i++) {  
            service.execute(new Runnable() {  
                @Override  
                public void run() {  
                    UnsafeCount.inc();  
                }  
            });  
        }  
  
        service.shutdown();  
        //避免出現(xiàn)main主線程先跑完而子線程還沒結(jié)束,在這里給予一個關(guān)閉時間  
        service.awaitTermination(3000,TimeUnit.SECONDS);  
        System.out.println("運行結(jié)果:UnsafeCount.count=" + UnsafeCount.count);  
    }  
}  
package com.game.lll.syn;  
  
public class LoggingWidget extends Widget{  
    public synchronized void doSomething()  
    {  
        System.out.println("LoggingWidget"+UnsafeCount.count++);  
        super.doSomething();  
    }  
}  
package com.game.lll.syn;  
public class Widget {  
   public synchronized void doSomething()  
   {  
       System.out.println("Widget"+UnsafeCount.count++);  
   }  
}  

控制臺輸出:

LoggingWidget0
Widget1
LoggingWidget2
Widget3
LoggingWidget4
Widget5
LoggingWidget6
Widget7
LoggingWidget8
Widget9
LoggingWidget10
Widget11
LoggingWidget12
Widget13
LoggingWidget14
Widget15
LoggingWidget16
Widget17
LoggingWidget18
Widget19
運行結(jié)果:UnsafeCount.count=20

3.2代碼分析

重進(jìn)入方便了鎖行為的封裝仆邓,因此簡化了面向?qū)ο蟛l(fā)代碼的開發(fā)鲜滩。上面代碼子類覆寫了父類的synchronized類型的方法伴鳖,并調(diào)用父類中的方法。如果沒有可重入的鎖绒北,這段代碼將會產(chǎn)生死鎖黎侈。因為Weight和loggingWeight中的soSomething方法都是synchronized類型的察署,都會在處理前試圖獲得weight的鎖闷游。倘若內(nèi)部鎖不是可重入的,super.doSomething的調(diào)用者就永遠(yuǎn)無法得到weight的鎖贴汪,因為鎖已經(jīng)被占有脐往,導(dǎo)致線程會永久的延遲,等待著一個永遠(yuǎn)無法獲得的鎖扳埂。

四业簿、鎖的三大特性

4.1 原子性

原子性是指在同一時刻只有一個線程對它進(jìn)行讀寫操作,避免多個線程在更改共享數(shù)據(jù)時出現(xiàn)數(shù)據(jù)的不準(zhǔn)確阳懂。

在Java中提供了原子操作的關(guān)鍵字synchronized梅尤。我在上一篇文章中寫過原子性Atomic(一)

4.2 可見性

可見性是指當(dāng)一個線程修改了線程共享變量的值,其它線程能夠立即得知這個值的修改岩调。在Java中巷燥,除了synchronized,volatile和final也是可見性的号枕。synchronized可以確保線程能預(yù)見另一個線程對某一個值或狀態(tài)的更改缰揪,就像下圖一樣。當(dāng)線程一執(zhí)行一個同步塊時葱淳,線程二也隨后進(jìn)入了同一個鎖的同步塊中钝腺,這時可以保證,在釋放鎖M之前線程一變量的值count對線程二是可見的赞厕。換句話說就是有一個玻璃透明的房間艳狐,雖然線程一進(jìn)入后將房間鎖住了皿桑,但是線程二在門口還是可以透過玻璃看見房間內(nèi)的一切事物。

image
package com.game.lll.syn;  
  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  
  
  
public class SafeCount implements Runnable{  
    public  volatile static int count = 0;  
  
    public synchronized static void inc()  
    {  
        count++;  
    }  
      
    public static void main(String[] args) throws InterruptedException {  
  
         SafeCount t1 = new SafeCount();    
         Thread ta = new Thread(t1, "線程一");    
         Thread tb = new Thread(t1, "線程二");    
         ta.start();    
         tb.start();    
        System.out.println("UnsafeCount.count=" + SafeCount.count);  
    }  
  
    @Override  
    public void run() {  
        System.out.println(Thread.currentThread().getName()+"---執(zhí)行前--count:"+count);    
        inc();  
        System.out.println(Thread.currentThread().getName()+"---執(zhí)行后--count:"+count);    
    }  
      
}  

控制臺輸出:

UnsafeCount.count=0
線程二---執(zhí)行前--count:0
線程一---執(zhí)行前--count:0
線程二---執(zhí)行后--count:1
線程一---執(zhí)行后--count:2

鎖不僅僅是關(guān)于同步與互斥浆西,也是關(guān)于內(nèi)存可見的近零。為了保證所有線程看到共享的、可變變量的最新值漓摩,讀寫和寫入線程必須使用公共的鎖進(jìn)行同步入客。摘自--《Java并發(fā)編程實戰(zhàn)》

4.3 有序性

Java語言提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性桌硫,volatile關(guān)鍵字本身就包含了禁止指令重排序的語義铆隘,而synchronized則是由“一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作”這條規(guī)則來獲得的膀钠,這個規(guī)則決定了持有同一個鎖的兩個同步塊只能串行地進(jìn)入肿嘲。

1.程序次序規(guī)則(Pragram Order Rule):在一個線程內(nèi)睦刃,按照程序代碼順序涩拙,書寫在前面的操作先行發(fā)生于書寫在后面的操作。準(zhǔn)確地說應(yīng)該是控制流順序而不是程序代碼順序工育,因為要考慮分支如绸、循環(huán)結(jié)構(gòu)怔接。

2.管程鎖定規(guī)則(Monitor Lock Rule):一個unlock操作先行發(fā)生于后面對同一個鎖的lock操作。這里必須強調(diào)的是同一個鎖瓦侮,而”后面“是指時間上的先后順序肚吏。罚攀。

3.volatile變量規(guī)則(Volatile Variable Rule):對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀取操作坞生,這里的”后面“同樣指時間上的先后順序是己。

4.線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法先行發(fā)生于此線程的每一個動作任柜。

5.線程終于規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對此線程的終止檢測摔认,我們可以通過Thread.join()方法結(jié)束宅粥,Thread.isAlive()的返回值等作段檢測到線程已經(jīng)終止執(zhí)行秽梅。

6.線程中斷規(guī)則(Thread Interruption Rule):對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生企垦,可以通過Thread.interrupted()方法檢測是否有中斷發(fā)生郑现。

7.對象終結(jié)規(guī)則(Finalizer Rule):一個對象初始化完成(構(gòu)造方法執(zhí)行完成)先行發(fā)生于它的finalize()方法的開始接箫。

8.傳遞性(Transitivity):如果操作A先行發(fā)生于操作B辛友,操作B先行發(fā)生于操作C瞎领,那就可以得出操作A先行發(fā)生于操作C的結(jié)論九默。

詳細(xì)請參考這篇文章:深入理解Java虛擬機筆記---原子性驼修、可見性墨礁、有序性

作者:小毛驢恩静,一個Java游戲服務(wù)器開發(fā)者 原文地址:https://liulongling.github.io/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市级乐,隨后出現(xiàn)的幾起案子风科,更是在濱河造成了極大的恐慌贼穆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崖蜜,死亡現(xiàn)場離奇詭異,居然都是意外死亡等恐,警方通過查閱死者的電腦和手機课蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吞获,“玉大人,你說我怎么就攤上這事烤黍〕跽” “怎么了奢入?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長议双。 經(jīng)常有香客問我,道長宗雇,這世上最難降的妖魔是什么赔蒲? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上舶沿,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好邑闲,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布褪子。 她就那樣靜靜地躺著,像睡著了一般笼痛。 火紅的嫁衣襯著肌膚如雪财忽。 梳的紋絲不亂的頭發(fā)上紧唱,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音漏益,去河邊找鬼蛹锰。 笑死,一個胖子當(dāng)著我的面吹牛绰疤,可吹牛的內(nèi)容都是我干的铜犬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轻庆,長吁一口氣:“原來是場噩夢啊……” “哼癣猾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起余爆,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤纷宇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蛾方,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體像捶,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年桩砰,在試婚紗的時候發(fā)現(xiàn)自己被綠了拓春。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡亚隅,死狀恐怖硼莽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枢步,我是刑警寧澤沉删,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站醉途,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏砖茸。R本人自食惡果不足惜隘擎,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凉夯。 院中可真熱鬧货葬,春花似錦、人聲如沸劲够。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽征绎。三九已至蹲姐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柴墩。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工忙厌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人江咳。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓逢净,卻偏偏與公主長得像,于是被迫代替她去往敵國和親歼指。 傳聞我的和親對象是個殘疾皇子爹土,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容