這次徹底搞懂并發編程的Balking模式
“多線程版本的if”來理解Guarded Suspension模式,不同于單線程中的if,這個“多線程版本的if”是需要等待的,而且還很執著,必須要等到條件為真。但很顯然這個世界,不是所有場景都需要這么執著,有時候我們還需要快速放棄。
需要快速放棄的一個最常見的例子是各種編輯器提供的自動保存功能。自動保存功能的實現邏輯一般都是隔一定時間自動執行存盤操作,存盤操作的前提是文件做過修改,如果文件沒有執行過修改操作,就需要快速放棄存盤操作。
下面的示例代碼將自動保存功能代碼化了,很顯然AutoSaveEditor這個類非線程安全,因為對共享變量changed的讀寫沒有使用同步,那如何保證AutoSaveEditor的線程安全性呢?
class AutoSaveEditor { // 文件是否被修改過 boolean changed=false; // 定時任務線程池 ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(); // 定時執行自動保存 void startAutoSave(){ ses.scheduleWithFixedDelay(()->{ autoSave(); }, 5, 5, TimeUnit.SECONDS); } // 自動存盤操作 void autoSave(){ if (!changed) { return; } changed = false; // 執行存盤操作 // 省略且實現 this.execSave(); } // 編輯操作 void edit() { ... changed = true; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
解決這個問題,最簡單直接對讀寫共享變量changed的方法autoSave()和edit()都加互斥鎖。雖然簡單,但性能很差,因為鎖的范圍太大了。
可以縮小鎖的粒度,只在讀寫共享變量changed的地方加鎖,如下:
void autoSave(){ synchronized(this){ if (!changed) { return; } changed = false; } // 存盤 this.execSave(); } void edit(){ ... synchronized(this){ changed = true; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
發現,示例中的共享變量是一個狀態變量,業務邏輯依賴于這個狀態變量的狀態:當狀態滿足某條件時,執行某個業務邏輯,其本質就是個if,放到多線程場景里,就是一種“多線程版本的if”。
這種“多線程版本的if”應用場景很多,有人就把它總結成了一種設計模式 - Balking模式。
實現Balking V1.0
Balking實質是規范化解決“多線程版本的if”的方案,使用Balking改造自動保存案例:
boolean changed = false; void autoSave(){ synchronized(this){ if (!changed) { return; } changed = false; } this.execSave(); } void edit(){ ... change(); } // 改變狀態 // 將edit()中對changed的賦值放進change(),達到解耦并發處理邏輯和業務邏輯 void change(){ synchronized(this){ changed = true; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
synchronized實現Balking最為穩妥,推薦工作中使用。
實現Balking V2.0
某些特定場景下,也可以使用volatile來實現,前置條件是不要求原子性。
比如RPC框架中,【本地路由表】要和【注冊中心】進行信息同步,應用啟動時,會將應用依賴服務的路由表從【注冊中心】同步到本地路由表中,如果應用重啟的時候【注冊中心】宕機,則會導致該應用依賴的服務均不可用,因為找不到依賴服務的路由表。
為防止這種極端情況出現,RPC框架可將本地路由表自動保存到本地文件,若重啟時,注冊中心宕機,那就從本地文件中恢復重啟前的路由表。這也是一種降級方案。
自動保存路由表和前面介紹的編輯器自動保存原理是一樣,也可用Balking模式,這里采用volatile。
因為對changed和rt的寫操作不要求原子性,且采用scheduleWithFixedDelay()這種調度方式,能保證同一時刻只有一個線程執行autoSave()
//路由表信息 public class RouterTable { //Key:接口名 //Value:路由集合 ConcurrentHashMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
說說其他應用場景?
class InitTest{ boolean inited = false; // 同步方法 synchronized void init(){ // 后續執行init()方法的線程就不會再執行doInit() if(inited){ return; } //省略doInit的實現 doInit(); // 第一次執行完時會將inited設置為true inited=true; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
總結
Balking模式和Guarded Suspension模式從實現上看似乎沒有多大的關系,Balking模式只需要用互斥鎖就能解決,而Guarded Suspension模式則要用到管程這種高級的并發原語;但是從應用的角度來看,它們解決的都是“線程安全的if”語義,不同之處在于,Guarded Suspension模式會等待if條件為真,而Balking模式不會等待。
Balking模式的經典實現是使用互斥鎖,你可以使用Java語言內置synchronized,也可以使用SDK提供Lock;如果你對互斥鎖的性能不滿意,可以嘗試采用volatile。
下面init()方法的本意是:僅需計算一次count的值,采用了Balking模式的volatile實現方式,你覺得這個實現是否有問題?
class Test{ volatile boolean inited = false; int count = 0; void init(){ if(inited){ return; } inited = true; //計算count的值 count = calc(); } }
1
2
3
4
5
6
7
8
9
10
11
12
NAT 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。