熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

多線程編程的設計模式 臨界區模式

2022-06-13   來源: Java高級技術 

  臨界區模式 Critical Section Pattern 是指在一個共享范圍中只讓一個線程執行的模式
它是所有其它多線程設計模式的基礎所以我首先來介紹它
把著眼點放在范圍上這個模式叫臨界區模式如果把作眼點放在執行的線程上這個模式就叫
單線程執行模式

  首先我們來玩一個鑽山洞的游戲我 Axman朋友 Sager同事 Pentium三個人在八角游樂場
循環鑽山洞(KAO減肥訓練啊)每個人手裡有一個牌子每鑽一次洞口的老頭會把當前的次序
姓名牌號顯示出來並檢查名字與牌號是否一致

  OK這個游戲的參與者有游樂場老頭GeezerPlayer就是我們還有山洞 corrie

  public class Geezer {
    public static void main(String[] args){
       
        Systemoutprintln(預備開始!);
        Corrie c = new Corrie();//只有一個山洞所以生存一個實例後傳給多個Player
        new Player(Axmanc)start();
        new Player(Sagerc)start();
        new Player(Pentiumc)start();
    }
}

  這個類暫時沒有什麼多說的它是一個Main的角色

  public class Player extends Thread{
    private final String name;
    private final String number;
    private final Corrie corrie;
    public Player(String nameString numberCorrie corrie) {
        thisname = name;
        thisnumber = number;
        rrie = corrie;
    }
   
    public void run(){
        while(true){
            rrieinto(thisnamethisnumber);
        }
    }
}
在這裡我們把成員字段都設成final的為了說明一個Player一旦構造他的名字和牌號就不能改
簡單說在游戲中SagerPentium三個人不會自己偷偷把自己的牌號換了也不會偷偷地去
鑽別的山洞如果這個游戲一旦發生錯誤那麼錯誤不在我們玩家

  import javautil*;
public class Corrie {
    private int count = ;
    private String name;
    private String number;
    private HashMap lib = new HashMap();//保存姓名與牌號的庫
   
    public Corrie(){
       
        libput(Axman);
        libput(Sager);
        libput(Pentium);
 
    }
   
    public void into(String nameString number){
        unt ++;
        thisname = name;
        thisnumber = number;
        if(thislibget(name)equals(number))
 test():
    }
   
    public String display(){
        return unt+: + thisname + ( + thisnumber + );
    }

  private void test(){
        if(thislibget(name)equals(number))
            ;
            //Systemoutprintln(OK: + display());
        else
            Systemoutprintln(ERR: + display());
    }
}

  這個類中增加了一個lib的HashMap相當於一個玩家姓名與牌號的庫因為明知道Corrie只有一個實例
所以我用了成員對象而不是靜態實例只是為了能在構造方法中初始化庫中的內容從真正意義中說應
該在一個輔助類中實現這樣的數據結構封裝的功能如果不提供這個lib那麼在check的時候就要用
if(nameequasl(Axman)){
 if(!numberequals()) //出錯
}
else if
這樣復雜的語句如果player大多可能會寫到手抽筋所以用一個lib來chcek就非常容象

  運行這個程序需要有一些耐心因為即使你的程序寫得再差在很多單線程測試環境下也能可是正確的
而且多線程程序在不同的機器上表現不同要發現這個例子的錯識可能要運行很長一段時間如果你的
機器是多CPU的那麼出現錯誤的機會就大好多

  在我的筆記本上最終出現錯誤是在分鐘以後出現的錯誤有幾鐘情況:
: ERR:Axman()
: ERR:Sager()
第一種情況是檢查到了錯誤我的牌號明明是卻打印出來而第二種明明沒有錯誤卻打印了錯誤

  事實上根據以前介紹的多線程知識不難理解這個例子的錯誤出現因為into不是線程安全的所以在其中
一個線程執行thisname = Axman;後本來應該執行thisnumner=卻被切換到另一個線程中執行
thisnumber=然後又經過不可預知的切換執行其中一個的if(thislibget(name)equals(number))
而出現的錯誤而在打印這個錯誤時因為display也不是線程安全的正要打印一個錯誤的結果時由於
thisname或thisnumber其中一個字段被修改卻成了正確的匹配而出現錯誤

  另外還有可能會出現序號顛倒或不對應但這個錯誤我們無法直觀地觀察因為你根本不知道哪個序號應該
給哪個Player而序號顛倒則有可能被滾動的屏幕所掩蓋

  [正確的Critical Section模式的例子]
我們知道出現這些錯誤是因為Corrie類的方法不是線程安全的那麼只要修改Corrie類為線程安全的類就行
其它類則不需要修改上面說過如果出現錯誤那一定不是我們玩家的事:

  import javautil*;
public class Corrie {
    private int count = ;
    private String name;
    private String number;
    private HashMap lib = new HashMap();//保存姓名與牌號的庫
   
    public Corrie(){
       
        libput(Axman);
        libput(Sager);
        libput(Pentium);
 
    }
   
    public synchronized void into(String nameString number){
        unt ++;
        thisname = name;
        thisnumber = number;
 test();
    }
   
    public synchronized String display(){
        return unt+: + thisname + ( + thisnumber + );
    }

  private void test(){
        if(thislibget(name)equals(number))
            ;
            //Systemoutprintln(OK: + display());
        else
            Systemoutprintln(ERR: + display());
    }
}

  運行這個例子如果你的耐心開著你的機器運行三天吧雖然測試天並不能說明第天沒有出錯
at least現在的正確性比原來那個沒有synchronized 保護的例子要可靠多了!

  到這裡我們對Critical Section模式的例程有了直觀的了解在詳細解說這個模式之前請想一下test
方法安全嗎?為什麼?


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27591.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.