一个线程如何在完成任务后返回一个值?

47

假设我们有这个简单的例子:

public Example extends Thread{

    String temp;    

    public Example(){
    }

    @Override
    public void run(){
        .
        .
        .
        .
        temp = "a_value";
    }

    public static void main(String[] args) {

        Example th = new Example();
        th.start();
    }

}

线程完成任务后如何将变量temp的值返回给我?


1
应该写成 th.start()。而且你已经实现了一种从线程中返回值的方法。 - Ha.
4个回答

67

使用相对较新的Callable<T>代替Runnable(1.5及更高版本可用):

这是一个(简单的)例子:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class Main {

    public static void main(final String[] argv) {
        final ExecutorService service;
        final Future<String>  task;

        service = Executors.newFixedThreadPool(1);        
        task    = service.submit(new Foo());

        try {
            final String str;

            // waits the 10 seconds for the Callable.call to finish.
            str = task.get(); // this raises ExecutionException if thread dies
            System.out.println(str);
        } catch(final InterruptedException ex) {
            ex.printStackTrace();
        } catch(final ExecutionException ex) {
            ex.printStackTrace();
        }

        service.shutdownNow();
    }
}

class Foo implements Callable<String> {
    public String call() {
        try {
            // sleep for 10 seconds
            Thread.sleep(10 * 1000);
        } catch(final InterruptedException ex) {
            ex.printStackTrace();
        }

        return ("Hello, World!");
    }
}

2
(相对)较新。相对于什么?它是在一个现已完成其服务期限的版本中引入的。 - Tom Hawtin - tackline
21
相对于发布,1.6是当前版本,它是在1.5中发布的。与Thread相比,它出现在1.0中...很多学生所学习的书籍中没有Callable。 - TofuBeer
2
foo是软件程序员版的《生活大爆炸》中的“Baznga” :) - Nicholas TJ
好样例!谢谢TofuBeer. :) - Martin Pfeffer
2
@TofuBeer,你能帮忙解释一下如何给线程方法调用(call())提供输入吗? :-) - Mohit
2
@Mohit,你不能这样做,因为API不允许你这样做。https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html#call-- 你可以做的是在Foo中创建成员变量,在构造函数中设置它们,并在调用方法中稍后使用它们。例如:task = service.submit(new Foo(1,"abc")); - SDS

12

查看Future接口的javadoc。它提供了示例用法以指导你如何操作。


8
你可以通过观察者模式实现这一点。 在线程完成时,通知所有侦听器它已经完成,并且它们可以通过getter获取值。或者它甚至可以立即发送计算出的值。
或者你可以使用任务,参见FutureTask,它是一个可运行的(确实如下所述的)Callable,返回结果并可以抛出异常。

我认为观察者模式是正确的答案。如果你无论如何都要阻塞执行等待结果,那么开启新线程的意义在哪里呢? - Enrique

0

如果您不想交换解决方法以使用Callable对象,则可以使用队列并以这种方式从线程返回结果。
我将您的示例重写为:

import java.util.PriorityQueue;
import java.util.Queue;

public class GetResultFromThread {
    public static void main(String[] args) throws Exception {
        Queue<String> queue = new PriorityQueue<String>();
        int expectedResults = 2;
        for (int i = 0; i < expectedResults; i++) {
            new Example(queue).start();
        }

        int receivedResults = 0;
        while (receivedResults < expectedResults) {
            if (!queue.isEmpty()) {
                System.out.println(queue.poll());
                receivedResults++;
            }
            Thread.sleep(1000);
        }
    }
}

class Example extends Thread {
    private final Queue<String> results;

    public Example(Queue<String> results) {
        this.results = results;
    }

    @Override
    public void run() {
        results.add("result from thread");
    }
}

请注意,您应该考虑同步和并发!

3
不仅仅是点击向下箭头并说这是一个不好的解决方案,如果您能解释一下为什么会更好。谢谢。 - thomasb
实际上,我认为@Martin的解决方案非常聪明,谢谢。 - Benvorth
请注意,此实现未同步。如果任何线程修改队列,则多个线程不应同时访问PriorityQueue实例。相反,请使用线程安全的PriorityBlockingQueue类。 - Radiodef

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接