帶你進入Synchronized關鍵字

1. 概覽


這篇文章將會介紹java中的同步代碼塊师幕。

在多線程環(huán)境中,當倆個或多個線程試圖在同一時間更新一個互斥共享數(shù)據(jù)時诬滩,就會產(chǎn)生競爭(race condition)霹粥。java提供了一整套機制去避免競爭,那就是當線程對共享數(shù)據(jù)訪問時進行同步synchronize操作疼鸟。


2. 為什么要同步后控?

我們來設想一個最典型的競爭場景,那就是在我們進行求和運算時空镜,有多個線程執(zhí)行calcute()方法:

public class BaeldungSynchronizedMethods?

????privateintsum = 0;

? public? void calculate() {

????????setSum(getSum() + 1);

????}

?// standard setters and getters

}

現(xiàn)在我們來寫一個簡單得測試:

@Test

? public void? givenMultiThread_whenNonSyncMethod() {

????ExecutorService service = Executors.newFixedThreadPool(3);

????BaeldungSynchronizedMethods summation = newBaeldungSynchronizedMethods();

? ? ?IntStream.range(0, 1000)

??????.forEach(count -> service.submit(summation::calculate));

????service.awaitTermination(1000, TimeUnit.MILLISECONDS);

? ? ?assertEquals(1000, summation.getSum());

}

很簡單浩淘,我們使用一個擁有3個線程池的ExecutorService 去運行1000次 calculte()方法。如果我們串行地運行吴攒,期望的結(jié)果是1000张抄,但是,幾乎每一次當我們使用多線程去執(zhí)行時舶斧,都是失敗的欣鳖,得到的是不一致的輸出結(jié)果

? e.g:

? java.lang.AssertionError: expected:<1000> but was:<965>

at org.junit.Assert.fail(Assert.java:88)

at org.junit.Assert.failNotEquals(Assert.java:834)

...

這個結(jié)果當然不是我們所期望的。

避免競爭的最簡單的方式是 : 通過使用 synchronized 關鍵字來讓我們的操作變成一個線程安全的操作茴厉。


3.?synchronized 關鍵字

synchronized關鍵字可以在不同的層次上使用:

? . 實例方法

? . 靜態(tài)方法

? . 代碼塊

? 當我們使用synchronized塊時泽台,java內(nèi)部會使用一個監(jiān)視器monitor(也就是大家熟知的什荣,monitor? lock 或 intrinsic lock) 去提供同步。這些monitor都會和一個對象綁定怀酷,因此稻爬,相同對象的所有同步代碼塊在同一時刻只會有一個線程在執(zhí)行。


3.1?同步實例方法


? ?簡單地把synchronized關鍵字放在方法聲明里就能讓方法同步:

? ? publicsynchronizedvoidsynchronisedCalculate() {

????setSum(getSum() + 1);

}

請注意蜕依,一旦我們同步了這個方法桅锄,這個測試案例就執(zhí)行通過了,并且輸出了1000:

@Test

publicvoidgivenMultiThread_whenMethodSync() {

????ExecutorService service = Executors.newFixedThreadPool(3);

????SynchronizedMethods method = newSynchronizedMethods();

????IntStream.range(0, 1000)

??????.forEach(count -> service.submit(method::synchronisedCalculate));

????service.awaitTermination(1000, TimeUnit.MILLISECONDS);

????assertEquals(1000, method.getSum());

}

實例方法將借助于該類的實例實現(xiàn)同步样眠,也就是意味著 該類的每一個實例只有一個線程能執(zhí)行同步方法友瘤。

3.2??同步靜態(tài)方法


? ? ?對靜態(tài)方法做同步就和實例方法一樣:

public static synchronized void? syncStaticCalculate() {

????staticSum = staticSum + 1;

}

使用該類所關聯(lián)的Class對象來對這些靜態(tài)方法同步。同時檐束,由于一個JVM中的每一個類只存在一個Class對象辫秧,因此,不管該類有多少實例數(shù)量被丧, 每個類都只有一個線程能執(zhí)行該靜態(tài)方法盟戏。

我們來測試一下:

@Test

publicvoidgivenMultiThread_whenStaticSyncMethod() {

????ExecutorService service = Executors.newCachedThreadPool();

? ? ?IntStream.range(0, 1000)

??????.forEach(count ->

????????service.submit(BaeldungSynchronizedMethods::syncStaticCalculate));

????service.awaitTermination(100, TimeUnit.MILLISECONDS);

????assertEquals(1000, BaeldungSynchronizedMethods.staticSum);

}


3.3?方法內(nèi)部的同步代碼塊

有時,我們并不想同步整個方法甥桂,而僅僅想同步的是該方法中的某一些代碼柿究。通過使用同步代碼塊可以實現(xiàn)這個目的。

publicvoid performSynchrinisedTask() {

????synchronized (this) {

????????setCount(getCount()+1);

????}

}

我們來測試一下這個變化:

@Test

publicvoidgivenMultiThread_whenBlockSync() {

????ExecutorService service = Executors.newFixedThreadPool(3);

????BaeldungSynchronizedBlocks synchronizedBlocks = newBaeldungSynchronizedBlocks();

????IntStream.range(0, 1000)

??????.forEach(count ->

????????service.submit(synchronizedBlocks::performSynchronisedTask));

????service.awaitTermination(100, TimeUnit.MILLISECONDS);

????assertEquals(1000, synchronizedBlocks.getCount());

}

請注意黄选,我們傳遞了一個this參數(shù)給同步代碼塊蝇摸,這是那個監(jiān)視器對象,代碼塊中的代碼會在監(jiān)視器對象上被同步办陷。簡單地說探入,每一個監(jiān)視器對象只有一個線程能執(zhí)行代碼塊中的代碼。

?當方法是靜態(tài)方法時懂诗,我們會傳遞類名稱來代替對象引用。并且這個Class對象將會是該同步代碼塊的監(jiān)視器苗膝。

public static void performStaticSyncTask(){

????synchronized (SynchronisedBlocks.class) {

????????setStaticCount(getStaticCount() + 1);

????}

}

? 我們來測試一下靜態(tài)方法中的同步代碼塊:

@Test

public void givenMultiThread_whenStaticSyncBlock() {

????ExecutorService service = Executors.newCachedThreadPool();

????IntStream.range(0, 1000)

??????.forEach(count ->

????????service.submit(BaeldungSynchronizedBlocks::performStaticSyncTask));

????service.awaitTermination(100, TimeUnit.MILLISECONDS);

????assertEquals(1000, BaeldungSynchronizedBlocks.getStaticCount());

}


5.?總結(jié)

在這篇文章中殃恒,我們看到了使用synchronized關鍵字來實現(xiàn)同步的不同方式。同時我們也探究了一個競爭(race condition) 是如何影響到我們的應用的以及同步是如何幫助我們避免的辱揭。 文章中的完整代碼請去https://github.com/eugenp/tutorials/tree/master/core-java-concurrency

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末离唐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子问窃,更是在濱河造成了極大的恐慌亥鬓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件域庇,死亡現(xiàn)場離奇詭異嵌戈,居然都是意外死亡覆积,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門熟呛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宽档,“玉大人,你說我怎么就攤上這事庵朝÷鹪” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵九府,是天一觀的道長椎瘟。 經(jīng)常有香客問我,道長侄旬,這世上最難降的妖魔是什么肺蔚? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮勾怒,結(jié)果婚禮上婆排,老公的妹妹穿的比我還像新娘。我一直安慰自己笔链,他們只是感情好段只,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鉴扫,像睡著了一般赞枕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坪创,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天炕婶,我揣著相機與錄音,去河邊找鬼莱预。 笑死柠掂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的依沮。 我是一名探鬼主播涯贞,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼危喉!你這毒婦竟也來了宋渔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辜限,失蹤者是張志新(化名)和其女友劉穎皇拣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薄嫡,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡氧急,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年颗胡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片态蒂。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡杭措,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤窘拯,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站泉懦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疹瘦。R本人自食惡果不足惜崩哩,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望言沐。 院中可真熱鬧邓嘹,春花似錦、人聲如沸险胰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽起便。三九已至棚贾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榆综,已是汗流浹背妙痹。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鼻疮,地道東北人怯伊。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像判沟,于是被迫代替她去往敵國和親震贵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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