2017-02-19 57 views
0

我有一個無限期運行的工作線程,如果沒有任何事情可以休眠一分鐘。有時候,另一段代碼會產生一些工作,並希望立即喚醒工作線程。喚醒線程而不冒被阻塞的風險

所以我做了這樣的事情(代碼僅供說明之用):

class Worker { 
    public void run() { 
     while (!shuttingDown()) { 
      step(); 
     } 
    } 

    private synchronized void step() { 
     if (hasWork()) { 
      doIt(); 
     } else { 
      wait(60_000); 
     } 
    } 

    public synchronized wakeMeUpInside() { 
     notify(); 
    } 
} 

我不喜歡什麼不必輸入顯示器只爲喚醒的東西了,這意味着該通知線程可能會延遲不好理由。由於本機同步的選擇是有限的,我想我會切換到Condition,但它有exactly the same problem

實現可能(並且通常確實)要求當前線程持有本條件時,關聯的鎖這個方法被調用。

+0

我想,我做的事情是傻瓜......其實,只有'wait'和'notify'需要被包含在同步塊中。還有另一個無關的同步,我混淆了...... – maaartinus

+0

我建議使用'BlockingQueue'。這樣你就可以忘記'synchronized'和'wait/notify'。當你關閉時,你只需要中斷'queue.poll()'等待的線程。 – Kayaman

+0

@Kayaman我知道'BlockingQueue',但我沒有把工作發送到線程,我只是通知它。而且,所有工作(由多個線程產生)可能或可能不會在一個步驟中執行。 – maaartinus

回答

2

這裏是一個信號量的解決方案:

class Worker { 
    // If 0 there's no work available 
    private workAvailableSem = new Semaphore(0); 

    public void run() { 
     while (!shuttingDown()) { 
      step(); 
     } 
    } 

    private synchronized void step() { 
     // Try to obtain a permit waiting up to 60 seconds to get one 
     boolean hasWork = workAvailableSem.tryAquire(1, TimeUnit.MINUTES); 
     if (hasWork) { 
      doIt(); 
     } 
    } 

    public wakeMeUpInside() { 
     workAvailableSem.release(1); 
    } 
} 

我不是100%肯定這符合您的需求。需要注意的一些事項:

  • 這將每次調用wakeMeUpInside時會添加一個許可證。因此,如果兩個線程喚醒Worker,它將兩次運行doIt而不會阻塞。您可以擴展該示例以避免這種情況。
  • 這需要等待60秒才能完成。如果沒有可用的,它將返回到run方法中,該方法將立即將其發送回step方法,該方法將再次等待。我這樣做是因爲我假設你有一些原因,即使沒有工作,你也希望每60秒運行一次。如果情況並非如此,只需致電aquire,您將無限期地等待工作。

根據下面的評論,OP只想運行一次。雖然你可以調用在這種情況下drainPermits一個清潔的解決方案就是使用LockSupport像這樣:

class Worker { 
    // We need a reference to the thread to wake it 
    private Thread workerThread = null; 
    // Is there work available 
    AtomicBoolean workAvailable = new AtomicBoolean(false); 

    public void run() { 
     workerThread = Thread.currentThread(); 
     while (!shuttingDown()) { 
      step(); 
     } 
    } 

    private synchronized void step() { 
     // Wait until work is available or 60 seconds have passed 
     ThreadSupport.parkNanos(TimeUnit.MINUTES.toNanos(1)); 
     if (workAvailable.getAndSet(false)) { 
      doIt(); 
     } 
    } 

    public wakeMeUpInside() { 
     // NOTE: potential race here depending on desired semantics. 
     // For example, if doIt() will do all work we don't want to 
     // set workAvailable to true if the doIt loop is running. 
     // There are ways to work around this but the desired 
     // semantics need to be specified. 
     workAvailable.set(true); 
     ThreadSupport.unpark(workerThread); 
    } 
} 
+0

一個好主意,但「它會兩次運行doIt'而不會阻塞」真的讓我困擾。我想,我可以在循環中吃掉所有的許可證。 – maaartinus

+0

如果你想讓它只運行一次只需調用'drainPermits' –

+0

但請不要刪除'Semophore'。 – maaartinus