从线程返回值


93

我有一个方法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();
}

如果主线程必须等待处理程序线程完成才能从方法返回,那么为什么要首先使用处理程序线程?
JB Nizet'2

1
@JBNizet我没有包括线程实际执行的复杂性。它正在获取GPS坐标,所以是的,我确实需要一个线程。
塔(Neeta)

2
不管线程的复杂程度如何,如果启动它的线程在启动后立即等待其结果,那么启动另一个线程是没有意义的:启动线程将被阻塞,就好像它自己在工作一样。
JB Nizet'2

@JBNizet我不太清楚你的意思..你介意用另一种方式解释它吗?
塔(Neeta)

1
线程用于能够在后台执行某些操作,并且能够在后台线程执行时执行其他操作。如果启动线程,然后立即阻塞直到线程停止,则您可以自己执行该线程完成的任务,这不会有任何区别,只是它会更简单。
JB Nizet'2

Answers:


75

您可以使用局部最终变量数组。该变量必须是非基本类型,因此可以使用数组。您还需要同步两个线程,例如使用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.
}

您也可以这样使用an Executor和a Callable

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();
}

3
不。该代码不正确。对值的访问未正确同步。
G. Blake Meike 2014年

5
由于CountDownLatch的内存一致性保证,我们实际上不需要显式同步值访问。值数组的创建发生在uiThread start(程序顺序规则)同步之前,与2分配给value [0](线程开始),该值发生在闩锁之前。 .await()(来自CountDownLatch的保证)发生在从value [0](程序顺序规则)读取之前。
亚当·扎克曼

看起来您对闩锁是正确的!...在这种情况下,run方法的同步是无用的。
G. Blake Meike 2014年

好点子。我必须从OP的代码中粘贴粘贴。已更正。
亚当·扎尔克曼


87

通常你会做这样的事情

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

然后,您可以创建线程并检索值(假设已设置值)

Foo foo = new Foo();
Thread thread = new Thread(foo);
thread.start();
thread.join();
int value = foo.getValue();

tl;dr线程无法返回值(至少不是没有回调机制)。您应该像普通类一样引用线程并要求其值。


2
这真的有效吗?我懂了The method getValue() is undefined for the type Thread
pmichna 2013年

1
@pmichna,很好的发现。从更改t.getValue()foo.getValue()
JohanSjöberg2013年

3
是! 做得好!“易失” ftw!与接受的答案不同,这是正确的!
G. Blake Meike 2014年

11
@HamzahMalik要确保线程完成使用Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();。该t.join()直到线程块完成。
丹尼尔(Daniel)

2
@HariKiran是的,每个线程返回一个独立的值。
rootExplorr

28

您正在寻找的可能是Callable<V>代替的接口Runnable,并使用一个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();
}

8

这个解决方案怎么样?

它不使用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()在完成之前调用它,那么调用它的线程将只是等待完成


我将提供的代码分为两件事:一是我在Application类中执行的池初始化(我在谈论android),其次我在需要它的地方使用池...也与使用Executors.newFixedThreadPool( 2)我使用了Executors.newSingleThreadExecutor()..因为我一次只需要运行一个任务即可进行服务器调用...您的答案非常完美@electirc咖啡,谢谢
Gaurav Pangam,2013年

@Electric您如何知道何时获得价值?我相信原始的发帖人希望以他的方法返回这个值。使用.get()调用将检索该值,但前提是该操作完成。他不知道有盲获得()调用
portfoliobuilder

4

如果要从调用方法中获取该值,则它应等待线程完成,这使得使用线程变得毫无意义。

为了直接回答您的问题,该值可以存储在任何可变对象中,调用方法和线程都可以引用。您可以使用external this,但除了琐碎的示例以外,它不会特别有用。

关于问题代码的一点说明:扩展Thread通常是较差的样式。实际上,不必要地扩展类是一个坏主意。我注意到您的run方法由于某种原因而被同步。现在,既然是这种情况下的对象,那么Thread您可能会干扰Thread使用其锁的任何对象(在参考实现中,与joinIIRC有关)。


“那么它应该等待线程完成,这使使用线程变得毫无意义”。
likejudo 2013年

4
这通常没有意义,但是在Android中,您无法向主线程上的服务器发出网络请求(以保持应用程序的响应速度),因此您必须使用网络线程。在某些情况下,您需要结果才能使应用恢复。
Udi Idan 2014年

2

从Java 8开始,我们有了CompletableFuture。根据您的情况,可以使用方法supplyAsync在执行后获取结果。

请找到一些参考。在这里https://www.baeldung.com/java-completablefuture#Processing

    CompletableFuture<Integer> completableFuture
      = CompletableFuture.supplyAsync(() -> yourMethod());

   completableFuture.get() //gives you the value

1

使用上面的答案中描述的Future可以完成任务,但不如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)
        {

        }
    };

1

只需对代码进行少量修改,即可以更通用的方式实现它。

 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. Handler在UI线程中创建一个称为responseHandler
  2. HandlerLooperUI Thread 初始化它。
  3. 在中HandlerThread,在此发布消息responseHandler
  4. handleMessgae显示Toast从消息中收到的with值。该Message对象是通用的,您可以发送不同类型的属性。

使用这种方法,您可以在不同时间点将多个值发送到UI线程。您可以在其上运行(发布)许多Runnable对象,HandlerThread并且每个对象都Runnable可以在Message对象中设置值,UI线程可以接收该值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.