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