2008-12-09 121 views
11

您認爲獲得線程工作結果的最佳方式是什麼?設想一個線程,它會做一些計算,你如何警告主程序的計算完成?獲取線程的輸出

你可以每隔X毫秒查詢一些名爲「工作完成」的公共變量或其他東西,但是隨後你會收到結果的時間晚於它們的可用時間......主代碼將會失去時間等着他們。另一方面,如果使用較低的X,那麼CPU會浪費很多次輪詢。

那麼,你怎麼做才能意識到線程或者某些線程已經完成了他們的工作?

對不起,如果它看起來類似於這個其他question,這可能是我回答的原因,我想。我所說的是運行大量線程,並知道它們什麼時候完成,而沒有輪詢它們。

我在考慮使用批處理線程在多個CPU之間共享CPU負載,並知道批處理完成的時間。我想這可以用未來的對象完成,但是阻止得到方法看起來很像一個隱藏的鎖,而不是我喜歡的東西。

感謝大家的支持。 Althought我也埃裏克森喜歡的答案,我想SAUA的最完整的,和一個我會在自己的代碼中使用。

+0

>並知道何時ab atch已完成。 你爲什麼想知道?你需要那批嗎?或者你想知道什麼時候可以安排更多任務? – 2008-12-09 15:25:40

回答

25

不要使用線程等低級構造,除非您絕對需要強大的功能和靈活性。

您可以使用ExecutorService,如ThreadPoolExecutorsubmit()Callables。這將返回一個Future對象。

使用Future對象,您可以輕鬆檢查它是否完成並獲得結果(如果尚未完成,則包括阻止get())。

這些結構將大大簡化最常見的線程操作。

我想澄清有關阻塞get()

的想法是,要運行該做一些工作的一些任務(在Callable S)(計算,資源訪問,...),您現在不需要結果。你只需要依靠Executor就可以隨時運行你的代碼(如果它是一個ThreadPoolExecutor,那麼只要有空閒線程可用,它就會運行)。然後在某個時間點,您可能需要需要計算結果繼續。此時你應該打電話給get()。如果該任務已經在該點運行,那麼get()將立即返回該值。如果任務沒有完成,那麼get()調用將等待,直到任務完成。這通常是需要的,因爲無論如何你都無法繼續完成任務。

當你不需要值繼續,但想知道,如果它已經存在(可能顯示在UI的東西),那麼你就可以輕鬆地調用isDone()和來電get()如果返回true )。

1

輪詢另外一個忙等待不是一個好主意。正如您所提到的,繁忙的等待會浪費CPU週期,並可能導致您的應用程序顯示無響應。

我的Java是粗糙的,但你要像下面這樣:

如果一個線程必須等待另一個線程的輸出,你應該使用條件變量。

final Lock lock = new ReentrantLock(); 
final Condition cv = lock.newCondition(); 

對另一個威脅的輸出感興趣的線程應該調用cv.wait()。這將導致當前線程阻塞。當工作線程完成工作時,應該調用cv.signal()。這將導致阻塞的線程變得暢通無阻,允許它檢查工作線程的輸出。

0

正如saua指出的那樣:使用由java.util.concurrent提供的構造。如果你被一個1.5(或5.0)以上的JRE所困住,你可能會採用自己的方式,但通過使用backport仍然更好:http://backport-jsr166.sourceforge.net/

1

作爲併發API如Saua所描述的(如果主線程不需要知道工作線程何時完成),則可以使用發佈/訂閱模式。

在這種情況下孩子Thread/Runnable被賦予了聽衆知道如何處理的結果,這就是所謂回Thread/Runnable完成時的孩子。

1

你的情況還是有點不清楚。

如果您正在運行批處理作業,則可能需要使用invokeAll。這將阻止您的主線程,直到完成所有任務。這種方法沒有「忙等待」,主線程會浪費CPU輪詢Future的isDone方法。雖然此方法返回的列表爲Futures,但它們已經「完成」。 (還有一個重載版本可能會在完成之前超時,這可能會更安全地用於某些任務。)這可能比嘗試收集一堆Future對象並嘗試檢查其狀態或阻止它們get個別方法。

如果這是一個交互式應用程序,並且零星分解爲要在後臺執行的任務,那麼使用nick.holt建議的callback是一種很好的方法。在這裏,您使用submit a Runnablerun方法在計算結果時調用回調。通過這種方法,除非您希望能夠在不關閉整個ExecutorService的情況下運行cancel任務,否則您可能會丟棄由submit返回的Future

如果您想要取消任務或使用超時功能,需要記住的重要一點是通過在其線程上調用interrupt來取消任務。所以,你的任務需要定期檢查它的interrupted status,並根據需要中止。

1

子類Thread,給你的類一個返回結果的方法。當該方法被調用時,如果結果尚未創建,然後再加入()與Thread。當join()返回時,線程的工作將完成並且結果應該可用;把它返還。

僅在實際需要觸發異步活動,在等待時執行一些工作,然後獲取結果時才使用此選項。否則,線程有什麼意義?您可能只需編寫一個可以完成工作並在主線程中返回結果的類。

另一種方法是回調:你的構造函數是否帶有一個參數,該參數實現了一個帶有回調方法的接口,在計算結果時會調用該方法。這將使工作完全異步。但是,如果你在某個時候需要等待結果,我認爲你仍然需要從主線程調用join()。

4

您可以創建一個主程序實現的lister接口,一旦它完成執行工​​作,工作人員就會調用它。

這樣你根本不需要輪詢。

下面是一個例子接口:

/** 
* Listener interface to implement to be called when work has 
* finished. 
*/ 
public interface WorkerListener { 
    public void workDone(WorkerThread thread); 
} 

下面是實際的主題,做一些工作,並通知它的聽衆的例子:

import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.List; 

/** 
* Thread to perform work 
*/ 
public class WorkerThread implements Runnable { 
    private List listeners = new ArrayList(); 
    private List results; 

    public void run() { 
     // Do some long running work here 

     try { 
      // Sleep to simulate long running task 
      Thread.sleep(5000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 

     results = new ArrayList(); 
     results.add("Result 1"); 

     // Work done, notify listeners 
     notifyListeners(); 
    } 

    private void notifyListeners() { 
     for (Iterator iter = listeners.iterator(); iter.hasNext();) { 
      WorkerListener listener = (WorkerListener) iter.next(); 
      listener.workDone(this); 
     } 
    } 

    public void registerWorkerListener(WorkerListener listener) { 
     listeners.add(listener); 
    } 

    public List getResults() { 
     return results; 
    } 
} 

最後,主程序其啓動一個工作線程並註冊一個工作完成後要通知的監聽器:

import java.util.Iterator; 
import java.util.List; 

/** 
* Class to simulate a main program 
*/ 
public class MainProg { 
    public MainProg() { 
     WorkerThread worker = new WorkerThread(); 
     // Register anonymous listener class 
     worker.registerWorkerListener(new WorkerListener() { 
      public void workDone(WorkerThread thread) { 
       System.out.println("Work done"); 
       List results = thread.getResults(); 
       for (Iterator iter = results.iterator(); iter.hasNext();) { 
        String result = (String) iter.next(); 
        System.out.println(result); 
       } 
      } 
     }); 

     // Start the worker thread 
     Thread thread = new Thread(worker); 
     thread.start(); 

     System.out.println("Main program started"); 
    } 

    public static void main(String[] args) { 
     MainProg prog = new MainProg(); 
    } 
}