2016-05-02 75 views
6

我有給定隨機數(1到n)的線程並被指示按排序順序打印它們。我使用了信號量,以便獲得許可證數量=隨機數,並獲得比獲得的更多的許可證。Java - 沒有獲取信號量版本

獲得=隨機數;釋放= 1 +隨機數

信號量的初始許可證計數爲1.因此,隨機數1的線程應該獲得許可證,然後是2等等。

這是支持按以下

給出的文件有()的釋放許可線程必須通過調用獲取已獲得該許可證沒有要求。

問題是我的程序在n> 2後卡住了1。

我的程序如下:

import java.util.concurrent.Semaphore; 

public class MultiThreading { 
    public static void main(String[] args) { 
     Semaphore sem = new Semaphore(1,false); 
     for(int i=5;i>=1;i--) 
      new MyThread(i, sem); 
    } 
} 
class MyThread implements Runnable { 
    int var;Semaphore sem; 
    public MyThread(int a, Semaphore s) { 
     var =a;sem=s; 
     new Thread(this).start(); 
    } 
    @Override 
    public void run() { 
     System.out.println("Acquiring lock -- "+var); 
     try { 
      sem.acquire(var); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 

     System.out.println(var); 

     System.out.println("Releasing lock -- "+var); 
     sem.release(var+1); 
    } 
} 

輸出是:

獲取鎖 - 4
獲取鎖 - 5
獲取鎖 - 3
獲取鎖 - 2
獲取鎖定 - 1
釋放鎖 - 1

雖然如果我用tryAcquire修改我的代碼,它運行得非常好。 下面是新的運行實施

@Override 
public void run() { 
    boolean acquired = false; 
    while(!acquired) { 
     acquired = sem.tryAcquire(var); 
    } 
    System.out.println(var); 
    sem.release(var+1); 
} 

有人可以解釋信號燈的許可證的獲取機制,當多張線程與不同的許可請求等待?

+1

我很抱歉,我不能回答你的問題,但。不**在構造函數中執行'new Thread(this).start();'。由於您仍在構造函數中,因此對象不完整,您將部分初始化的對象提供給另一個方法,在這種情況下甚至會傳送給另一個線程。這真的很糟糕,不要這樣做。更好'擴展線程'而不是'實現Runnable',然後執行'new MyThread(i,sem).start();'或執行'new Thread(new MyThread(i,sem))。start();'。 – Vampire

+0

噢,我會驗證這一點。感謝您指出。 – user1474053

+0

@BjörnKautler而不是僅僅說「*這真的很糟糕,不要這樣做*。」試圖解釋*爲什麼*所以人們可以理解潛在的問題。 – dimo414

回答

4

這是一個聰明的策略,但你誤解Sempahore如何取得許可。如果你運行你的代碼足夠的時間,你會真正看到它達到步驟二:

Acquiring lock -- 5 
Acquiring lock -- 1 
1 
Releasing lock -- 1 
Acquiring lock -- 3 
Acquiring lock -- 2 
2 
Acquiring lock -- 4 
Releasing lock -- 2 

如果繼續再運行它足夠的時間,你會真正看到它成功完成。發生這種情況是因爲Semaphore實施許可證。假設Semaphore只要有足夠的許可證就可以嘗試容納acquire()呼叫。如果我們在爲Semaphore.aquire(int)文件仔細看,我們會看到,是不是這種情況(重點煤礦):

如果足夠的可用許可,那麼當前線程用於線程調度目的,禁用並一直處於休眠狀態.. 。其他一些線程調用release這個信號量的方法之一,當前線程接下來將被分配許可證,並且可用許可證的數量滿足這個請求。

換句話說Semaphore保持未決獲取請求的隊列,並且在每次調用.release()只檢查隊列的頭部。特別是如果您啓用公平排隊(將第二個構造函數參數設置爲true),您甚至會發現第一步不會發生,因爲步驟5(通常)是隊列中的第一個,甚至可以實現的新呼叫將會排在其他未決呼叫之後。

簡而言之,這意味着您不能依賴於.acquire()儘快返回,因爲您的代碼假定。

通過在循環中使用.tryAcquire()代替你避免作出任何阻塞調用(因此投入了大量的更多的負載您Semaphore),並儘快允許所需數量的可用一個tryAcquire()通話將順利獲得它們。這有效,但是很浪費。

在餐廳畫一張等候名單。使用.aquire()就像在名單上列出你的名字並等待被調用。這可能不是很有效,但他們會在合理的時間內找到你。想象一下,如果每個人都只是在主持人喊道:「你有沒有n的桌子?」儘可能經常 - 這是您的tryAquire()循環。它可能仍然有效(就像它在你的例子中那樣),但它肯定不是正確的方法。


那麼你應該怎麼做呢? java.util.concurrent中有許多可能有用的工具,最好在某種程度上取決於你想要做什麼。看到每個線程都有效地開始下一個線程時,我可能會使用BlockingQueue作爲同步輔助,每次都會將下一步推入隊列。然後每個線程輪詢隊列,如果不是被激活的線程輪到,則替換該值並再次等待。

下面是一個例子:

public class MultiThreading { 
    public static void main(String[] args) throws Exception{ 
    // Use fair queuing to prevent an out-of-order task 
    // from jumping to the head of the line again 
    // try setting this to false - you'll see far more re-queuing calls 
    BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1, true); 
    for (int i = 5; i >= 1; i--) { 
     Thread.sleep(100); // not necessary, just helps demonstrate the queuing behavior 
     new MyThread(i, queue).start(); 
    } 
    queue.add(1); // work starts now 
    } 

    static class MyThread extends Thread { 
    int var; 
    BlockingQueue<Integer> queue; 

    public MyThread(int var, BlockingQueue<Integer> queue) { 
     this.var = var; 
     this.queue = queue; 
    } 

    @Override 
    public void run() { 
     System.out.println("Task " + var + " is now pending..."); 
     try { 
     while (true) { 
      int task = queue.take(); 
      if (task != var) { 
      System.out.println(
       "Task " + var + " got task " + task + " instead - re-queuing"); 
      queue.add(task); 
      } else { 
      break; 
      } 
     } 
     } catch (InterruptedException e) { 
     // If a thread is interrupted, re-mark the thread interrupted and terminate 
     Thread.currentThread().interrupt(); 
     return; 
     } 

     System.out.println("Finished task " + var); 

     System.out.println("Registering task " + (var + 1) + " to run next"); 
     queue.add(var + 1); 
    } 
    } 
} 

這將打印以下內容併成功終止:

Task 5 is now pending... 
Task 4 is now pending... 
Task 3 is now pending... 
Task 2 is now pending... 
Task 1 is now pending... 
Task 5 got task 1 instead - re-queuing 
Task 4 got task 1 instead - re-queuing 
Task 3 got task 1 instead - re-queuing 
Task 2 got task 1 instead - re-queuing 
Finished task 1 
Registering task 2 to run next 
Task 5 got task 2 instead - re-queuing 
Task 4 got task 2 instead - re-queuing 
Task 3 got task 2 instead - re-queuing 
Finished task 2 
Registering task 3 to run next 
Task 5 got task 3 instead - re-queuing 
Task 4 got task 3 instead - re-queuing 
Finished task 3 
Registering task 4 to run next 
Task 5 got task 4 instead - re-queuing 
Finished task 4 
Registering task 5 to run next 
Finished task 5 
Registering task 6 to run next 
+0

奇妙的做法。非常感謝,我現在明白了。我們也可以使用Atomic變量而不是BlockingQueue,或者可以使用Condition來避免輪詢機制。 – user1474053

+0

@ user1474053很樂意提供幫助。根據你想要完成的事情,肯定有許多不同的合理解決方案,但是如果你想確保單獨的線程按照規定的順序運行,你應該使用某種阻塞機制;所有線程都需要定期輪詢Atomic變量。 – dimo414

2

Semaphore.acquire(int)的Javadoc說:

If insufficient permits are available then the current thread becomes 
disabled for thread scheduling purposes and lies dormant until one of 
two things happens: 

Some other thread invokes one of the release methods for this semaphore, 
the current thread is next to be assigned permits and the number of 
available permits satisfies this request [or the thread is interrupted]. 

線程是「下一個被分配」在你的例子可能是線程4。它正在等待,直到有4個許可證可用。但是,在調用acquire()時獲得許可的線程1僅釋放2個許可,這不足以解除對線程4的阻塞。同時,線程2(它是唯一有足夠許可的線程)不是下一個被分配,所以它沒有得到許可證。

修改後的代碼運行良好,因爲線程在嘗試獲取信號時不會阻塞;他們只是再試一次,走到後面。最終,線程2到達線路的前端,並因此被分配,因此獲得許可。

+1

很好的解釋。謝謝你的時間。我錯過了解「當前的線程是旁邊被分配的許可證」行。 – user1474053