2014-11-25 50 views
0

我一直在聖誕老人的工作坊裏面提出一個問題,在這個工作坊裏,很多事情都必須發生,才能交付禮物。一個這樣的要求是11個精靈成功地生產了100件禮物。我的主類看起來是這樣的:如何正確使用信號燈

public class Main { 

    public volatile int toyList = 100; 

    public int getToyList() { 
     return toyList; 
    } 

    public void setToyList(int toyList) { 
     this.toyList = toyList; 
    } 

    public static void main(String[] args){ 
     Main main = new Main(); 

     ... 

     Elf elfOne = new Elf("Jim", main); 
     Elf elfTwo = new Elf("John", main); 
     Elf elfThree = new Elf("Eamonn", main); 
     Elf elfFour = new Elf("Eoin", main); 
     Elf elfFive = new Elf("Ronan", main); 
     Elf elfSix = new Elf("Seamus", main); 
     Elf elfSeven = new Elf("Rebecca", main); 
     Elf elfEight = new Elf("Orla", main); 
     Elf elfNine = new Elf("Tina", main); 
     Elf elfTen = new Elf("Filly", main); 
     Elf elfEleven = new Elf("Jess", main); 


     ... 

     elfOne.start(); 
     elfTwo.start(); 
     elfThree.start(); 
     elfFour.start(); 
     elfFive.start(); 
     elfSix.start(); 
     elfSeven.start(); 
     elfEight.start(); 
     elfNine.start(); 
     elfTen.start(); 
     elfEleven.start(); 

    } 

和我的精靈類看起來是這樣的:

public class Elf extends Thread{ 

    Semaphore semaphore = new Semaphore(11); 
    Random rng = new Random(); 
    String name; 
    Main main; 

    public Elf(String name, Main main){ 
     this.name = name; 
     this.main = main; 
    } 

    public void run(){ 
     while(true){ 
       try { 
        semaphore.acquire(); 
        makeToy(); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } finally { 
        semaphore.release(); 
       } 
     } 
    } 

    private void makeToy() throws InterruptedException{ 
     if (main.getToyList() != 0){ 
      if(rng.nextInt(99) <= 4){ //5% chance to fail 
       System.out.println(name + " failed in making a toy!"); 
      }else{ 
       main.setToyList(main.getToyList() - 1); 
       System.out.println(name + " created a toy! Toys remaining: " + main.getToyList()); 
      } 
     } else { 
      wakeSanta(); 
     } 
    } 

    private void wakeSanta() throws InterruptedException { 

    } 

} 

示例輸出我得到的是:

... 
Eamonn created a toy! Toys remaining: 6 
Jim created a toy! Toys remaining: 7 
Eoin created a toy! Toys remaining: 8 
Jess created a toy! Toys remaining: 9 
Filly created a toy! Toys remaining: 0 

我想輸出得到的是它會依次遞減一個從100到0的隨機Elf。這是否像我的信號量獲取位置錯誤或者我完全誤解了信號量那樣簡單?

+1

你試圖保護的資源是一個int。除非您的任務明確要求信號量,否則稍後會將玩具列表轉換爲其他結構,使用AtomicInteger也可以完成這項工作。沒有管理信號量的複雜性。 – Ralf 2014-11-25 10:07:19

回答

1

是的,你的信號燈使用不正確。信號量實例可以授予許多權限。

  • 一個線程可以acquire()一個權限 - 阻止,直到權限可用,然後減少可用權限的數量。
  • 一個線程可以release()權限 - 增加可用權限的數量。

在您的代碼中,每個線程都擁有自己的信號燈並具有11權限。首先,作爲一種同步手段,信號量通常只有在多線程共享時纔有意義。其次,如果你想製作一個資源(在你的情況下是toyList)完全獨佔,信號量應該只提供一個許可。

想想這樣:爲了製作玩具,精靈們需要使用一臺機器。現在你的場景要求只有一個精靈可以同時製作一個玩具,這相當於只有一個玩具製造機器的車間。因此 - 在代碼中 - 您需要創建一臺機器的等價物:一個Semaphore - 只有一個權限的實例。好了,讓我們把SemaphoreMain -class:

public class Main { 

    Semaphore semaphore = new Semaphore(1); 
    // the rest of Main is kept the same 
} 

現在精靈需要獲得訪問玩具manufactoring機,所以不是semaphore.acquire(),我們使用main.semaphore.aquire()。但是如果機器在撥打run()並且從未發佈時佔用,那麼開始工作的第一個精靈將永遠不會讓其他精靈擁有機器。所以聖誕老人放下一些規則上的機器是如何被使用的:

  • 機器不能,除非精靈嘗試佔領製作玩具
  • 機器必須進行一次釋放的精靈(成功與否)完成製作玩具

所以run()makeToy()變化的代碼如下:

public void run(){ 
    while(true){ // sidenote: I would rearrange the nesting: Wrap the while-loop in the try-catch 
     try { 
      makeToy(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

private void makeToy() throws InterruptedException{ 
    main.semaphore.acquire(); 
    if (main.getToyList() != 0){ 
     if(rng.nextInt(99) <= 4){ //5% chance to fail 
      System.out.println(name + " failed in making a toy!"); 
     }else{ 
      main.setToyList(main.getToyList() - 1); 
      System.out.println(name + " created a toy! Toys remaining: " + main.getToyList()); 
     } 
    } else { 
     wakeSanta(); 
    } 
    main.semaphore.release(); 
} 

如果您想知道何時使用具有多個權限的信號量,請在此處舉一個例子:

假設您有一個經常閱讀和偶爾編寫的資源。因爲讀取不會改變該值(即,改變狀態),所以多個線程同時讀取該值是安全的。假設我們有n線程和一個帶n權限的信號量。一個線程現在必須獲得一個讀取權限 - 這意味着所有線程可以同時讀取,但它必須獲得允許寫入的所有權限 - 意味着當一個線程正在寫入時,其他線程不能訪問資源(既不讀也不寫)。


我個人認爲,獲取和釋放一個信號量與單一許可作爲​​- 阻塞的。但是,請注意用戶Ralf在註釋中指出的重要區別:無論哪個線程實際獲得權限,任何線程都可以釋放信號量。信號量更具表現力 - 缺點是使用錯誤更容易。

+1

信號量和互斥/同步塊之間有一個重要區別:同步塊放置的鎖具有線程相關性,信號量沒有。即在同步代碼的情況下,只有放置鎖的線程才能釋放它。但是信號量可以由一個線程遞減並遞增。所以這兩者之間存在很大的語義差異。 – Ralf 2014-11-25 10:10:52

+0

@Ralf非常感謝您提供這些重要信息。思考信號量和'synchronized''關鍵字的相似性一直幫助我理解這兩者,但是我從來沒有想到可以實現一個不同於獲取它的線程發佈的信號量。爲了將來的參考,我根據你的評論保留了更新的類比,但將它移到底部,因爲它有點偏離主題。 – 2014-11-25 13:40:42

1

從概念上講,信號量維護一組許可證。信號量只是保持可用數量的計數並相應地執行。

調用acquire()會阻塞,直到許可證可用(大於零),然後把它(倒數一)。每個版本()都添加一個許可證。

你可以認爲二年級是一個控制人們在門上訪問的門衛(需要控制線程訪問的邏輯)。他有一些鑰匙(許可證)進入這個門()。

當一個人(線程)需要進入這個門,他需要一個鑰匙(acquire()),或者他等到他拿到鑰匙(線程塊)。一個人離開門後,他應該退回鑰匙(release())以便其他人可以得到。

所以在你現有的代碼中,每個線程都有11個鍵可以獲得,這是沒有意義的,因爲每個人現在都有一個獨立的後衛,他有11個鍵。

如果您需要同時控制只有一個線程makeToy()。你應該只使用一個許可證來使用信號量。

Semaphore semaphore = new Semaphore(1); 

,這應該是一個公共信號,其中線股,所以你可能不應該實例化一個線程內的信號。

你可以試試這個:

Main main = new Main(); 
    Semaphore semaphore = new Semaphore(1,true);//Only allow one thread to enter,so totally one permit is avaiable, and fairness =true 
    Elf elfOne = new Elf("Jim", main,semaphore);//pass the semaphore to all the threads 
    Elf elfTwo = new Elf("John", main,semaphore); 
    Elf elfThree = new Elf("Eamonn", main,semaphore); 
    Elf elfFour = new Elf("Eoin", main,semaphore); 
    ..... 


public class Elf extends Thread{ 

    Semaphore semaphore; 
    Random rng = new Random(); 
    String name; 
    Main main; 

    public Elf(String name, Main main,Semaphore semaphore){// need semaphore in constructors 
     this.name = name; 
     this.main = main; 
     this.semaphore=semaphore; 
    } 

這裏的變化代碼的輸出:

... 
Filly created a toy! Toys remaining: 79 
Eamonn created a toy! Toys remaining: 78 
Eoin created a toy! Toys remaining: 77 
Jess created a toy! Toys remaining: 76 
Ronan created a toy! Toys remaining: 75 
Jim failed in making a toy! 
John created a toy! Toys remaining: 74 
Seamus created a toy! Toys remaining: 73 
Rebecca created a toy! Toys remaining: 72 
...