這次徹底搞懂并發編程的Balking模式

      網友投稿 734 2022-05-28

      “多線程版本的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

      這次徹底搞懂并發編程的Balking模式

      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> rt = new ConcurrentHashMap<>(); //路由表是否發生變化 volatile boolean changed; //將路由表寫入本地文件的線程池 ScheduledExecutorService ses= Executors.newSingleThreadScheduledExecutor(); //啟動定時任務 //將變更后的路由表寫入本地文件 public void startLocalSaver(){ ses.scheduleWithFixedDelay(()->{ autoSave(); }, 1, 1, MINUTES); } //保存路由表到本地文件 void autoSave() { if (!changed) { return; } changed = false; //將路由表寫入本地文件 //省略其方法實現 this.save2Local(); } //刪除路由 public void remove(Router router) { Set set=rt.get(router.iface); if (set != null) { set.remove(router); //路由表已發生變化 changed = true; } } //增加路由 public void add(Router router) { Set set = rt.computeIfAbsent( route.iface, r -> new CopyOnWriteArraySet<>()); set.add(router); //路由表已發生變化 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

      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小時內刪除侵權內容。

      上一篇:LabVIEW操作鼠標滾輪放大/縮小圖像
      下一篇:操作系統學習筆記(二十八)~文件系統+連續分配+鏈接分配+索引分配+空閑空間管理
      相關文章
      亚洲图片中文字幕| 亚洲欧洲一区二区三区| 亚洲精品无码AV中文字幕电影网站| 狠狠色香婷婷久久亚洲精品| 亚洲黄色免费观看| 亚洲午夜精品一区二区| 日韩va亚洲va欧洲va国产| 国产亚洲美女精品久久久| 亚洲人成无码网WWW| 亚洲性日韩精品一区二区三区| 亚洲&#228;v永久无码精品天堂久久 | 亚洲美国产亚洲AV| 亚洲精品一卡2卡3卡四卡乱码| 亚洲午夜无码久久久久软件| 亚洲熟妇无码av另类vr影视| 亚洲乱亚洲乱妇无码| 亚洲AV无码成人网站在线观看| 亚洲一区二区三区丝袜| 亚洲欧美中文日韩视频| 亚洲精品乱码久久久久久蜜桃图片| 亚洲乱码无人区卡1卡2卡3| 精品无码专区亚洲| 春暖花开亚洲性无区一区二区| 无码天堂亚洲国产AV| 亚洲日本中文字幕天堂网| 黑人大战亚洲人精品一区| 亚洲精品高清国产一线久久| 无码乱人伦一区二区亚洲一| 亚洲视频一区二区三区| 亚洲一级毛片免观看| 亚洲一久久久久久久久| 在线精品自拍亚洲第一区| 亚洲一区二区三区国产精品| 亚洲精品白浆高清久久久久久| 久久亚洲精品无码| 亚洲午夜电影在线观看| 亚洲一本一道一区二区三区| 亚洲国产精品成人久久蜜臀| 亚洲综合伊人久久大杳蕉| 亚洲卡一卡2卡三卡4卡无卡三| 亚洲成人一级电影|