2012-02-05 112 views
60

我有一個HandlerThread的方法。值在Thread內部發生變化,我想將其返回到test()方法。有沒有辦法做到這一點?從線程返回值

public void test() 
{ 
    Thread uiThread = new HandlerThread("UIHandler"){ 
     public synchronized void run(){ 
      int value; 
      value = 2; //To be returned to test() 
     } 
    }; 
    uiThread.start(); 
} 
+3

這是什麼語言嗎? – 2012-02-05 11:38:01

+0

如果主線程必須等待處理線程在從方法返回之前完成,爲什麼首先使用處理線程呢? – 2012-02-05 11:43:14

+0

@JBNizet我沒有包含Thread實際所做的複雜性。它獲得gps座標,所以是的,我確實需要一個線程。 – Neeta 2012-02-05 11:59:26

回答

47

您可以使用本地最終變量數組。該變量需要是非原始類型的,所以你可以使用一個數組。您還需要使用CountDownLatch兩個線程同步,例如:

public void test() 
{ 
    final CountDownLatch latch = new CountDownLatch(1); 
    final int[] value = new int[1]; 
    Thread uiThread = new HandlerThread("UIHandler"){ 
     @Override 
     public void run(){ 
      value[0] = 2; 
      latch.countDown(); // Release await() in the test thread. 
     } 
    }; 
    uiThread.start(); 
    latch.await(); // Wait for countDown() in the UI thread. Or could uiThread.join(); 
    // value[0] holds 2 at this point. 
} 

你也可以使用一個ExecutorCallable這樣的:

public void test() throws InterruptedException, ExecutionException 
{ 
    ExecutorService executor = Executors.newSingleThreadExecutor(); 
    Callable<Integer> callable = new Callable<Integer>() { 
     @Override 
     public Integer call() { 
      return 2; 
     } 
    }; 
    Future<Integer> future = executor.submit(callable); 
    // future.get() returns 2 or raises an exception if the thread dies, so safer 
    executor.shutdown(); 
} 
+1

嗯......不。此代碼不正確。訪問值不正確同步。 – 2014-06-14 14:39:04

+3

由於CountDownLatch的內存一致性保證,我們實際上並不需要顯式同步訪問值。值數組的創建發生在uiThread啓動(程序順序規則)之前 - 與賦值2到值[0]([thread start](http://docs.oracle.com/javase/specs/jls/se7) /html/jls-17.html#jls-17.4.4))在latch.await()(發生在CountDownLatch的保證)之前發生的latch.countDown()(程序順序規則)從值[0](程序順序規則)。 – 2014-08-22 18:09:31

+0

看起來你是對的關於閂鎖! ...在這種情況下,運行方法的同步是無用的。 – 2014-08-22 18:39:00

48

通常你會做這樣的事情

public class Foo implements Runnable { 
    private volatile int value; 

    @Override 
    public void run() { 
     value = 2; 
    } 

    public int getValue() { 
     return value; 
    } 
} 

然後你就可以創建線程和檢索值(假設值已設置)

Foo foo = new Foo(); 
new Thread(foo).start(); 
// ... join through some method 
int value = foo.getValue(); 

tl;dr一個線程無法返回一個值(至少不是沒有回調機制)。你應該像一個普通的課程一樣引用一個線程並詢問其價值。

+2

這真的有用嗎?我得到'方法getValue()對於Thread類型是未定義的。 – pmichna 2013-11-26 15:07:08

+1

@pmichna,很好的發現。從't.getValue()'改爲'foo.getValue()'。 – 2013-11-26 15:46:26

+0

@JohanSjöberg:Tnx爲你的答案。但它有一個小錯誤。你應該叫「新線程(foo).start()」; – mostafa88 2014-05-29 08:37:41

21

您正在尋找的可能是代替RunnableCallable<V>接口,並通過Future<V>對象檢索值,這也可以讓您等待,直到計算出該值。您可以使用ExecutorService來實現此目的,您可以從Executors.newSingleThreadExecutor()獲得該值。

public void test() { 
    int x; 
    ExecutorService es = Executors.newSingleThreadExecutor(); 
    Future<Integer> result = es.submit(new Callable<Integer>() { 
     public Integer call() throws Exception { 
      // the other thread 
      return 2; 
     } 
    }); 
    try { 
     x = result.get(); 
    } catch (Exception e) { 
     // failed 
    } 
    es.shutdown(); 
} 
4

如果你想從調用方法的價值,那麼它應該等待線程完成,這使得使用線程有點沒有意義。

要直接回答你的問題,該值可以存儲在調用方法和線程都有引用的任何可變對象中。你可以使用外部的this,但除了一些簡單的例子之外,這不會特別有用。

關於代碼在問題上的一點注意:擴展Thread通常是不好的風格。事實上,不必要地擴展類是一個壞主意。我注意到你run方法由於某種原因被同步。現在,因爲在這種情況下的對象是Thread,您可能會干預Thread使用其鎖的任何內容(在參考實現中,與join,IIRC有關)。

+0

「那麼它應該等待線程完成,這使得使用線程有點沒有意義」偉大的一點! – likejiujitsu 2013-03-04 19:46:00

+3

它通常毫無意義,但在Android中,您無法向主線程上的服務器發出網絡請求(以保持應用程序響應),因此您必須使用網絡線程。 在應用程序可以恢復之前,有些情景需要結果。 – 2014-10-23 01:34:51

5

這個解決方案如何?

它不使用Thread類,但它是併發的,並且在某種程度上,它不正是你要求什麼

ExecutorService pool = Executors.newFixedThreadPool(2); // creates a pool of threads for the Future to draw from 

Future<Integer> value = pool.submit(new Callable<Integer>() { 
    @Override 
    public Integer call() {return 2;} 
}); 

現在你要做的就是說value.get()時,你需要抓住你的返回的值,線程開始第二秒,你給value一個值,所以你永遠不必說threadName.start()就可以了。

什麼Future是,是承諾的程序,你保證你會得到它,它有時需要在不久的將來值的程序

如果你調用它的.get()它完成之前,這是調用它只是簡單地等待,直到它完成

+0

我將提供的代碼分成兩部分,一是我在Application類(我正在談論android)中做的池初始化,其次我在需要它的地方使用池...另外關於使用Executors.newFixedThreadPool (2)我使用了Executors.newSingleThreadExecutor()..因爲我只需要一次運行服務器調用的任務...你的答案是完美的@electirc咖啡謝謝 – 2016-12-10 10:07:58

1

使用未來在上述答案中描述的線程做的工作,但少了幾分顯著爲f.get(),阻塞線程,直到它得到的結果,這違反併發性。

最好的解決辦法是使用番石榴的ListenableFuture。舉例:

ListenableFuture<Void> future = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1, new NamedThreadFactory).submit(new Callable<Void>() 
    { 
     @Override 
     public Void call() throws Exception 
     { 
      someBackgroundTask(); 
     } 
    }); 
    Futures.addCallback(future, new FutureCallback<Long>() 
    { 
     @Override 
     public void onSuccess(Long result) 
     { 
      doSomething(); 
     } 

     @Override 
     public void onFailure(Throwable t) 
     { 

     } 
    }; 
0

對代碼進行小小的修改,可以用更通用的方式實現它。

final Handler responseHandler = new Handler(Looper.getMainLooper()){ 
      @Override 
      public void handleMessage(Message msg) { 
       //txtView.setText((String) msg.obj); 
       Toast.makeText(MainActivity.this, 
         "Result from UIHandlerThread:"+(int)msg.obj, 
         Toast.LENGTH_LONG) 
         .show(); 
      } 
     }; 

     HandlerThread handlerThread = new HandlerThread("UIHandlerThread"){ 
      public void run(){ 
       Integer a = 2; 
       Message msg = new Message(); 
       msg.obj = a; 
       responseHandler.sendMessage(msg); 
       System.out.println(a); 
      } 
     }; 
     handlerThread.start(); 

解決方案:

  1. 創建UI線程,這被稱爲responseHandler
  2. 從UI線程的Looper初始化此Handler一個Handler
  3. HandlerThread,在此responseHandler
  4. handleMessgae交消息顯示一個Toast從消息中接收的值。此Message對象是通用的,您可以發送不同類型的屬性。

使用這種方法,您可以在不同的時間點向UI線程發送多個值。您可以運行該HandlerThread(POST)許多Runnable對象和每個Runnable可以Message的對象,可以通過UI線程接收設定值。

0

的Java 8提供CompletableFuture。這將是一站式解決方案。 http://www.baeldung.com/java-completablefuture

+1

這更像是一個反對評論的答案。您是否希望通過示例來加強它(甚至是從鏈接中複製並粘貼),以便一旦鏈接無效,人們仍然可以找到有用的答案。 – 2018-02-23 10:23:37