使用Callable返回结果

本文介绍Java中的Callable接口及其使用方法,对比Runnable接口,Callable允许任务返回结果。通过ExecutorService提交Callable任务,并利用Future获取结果。

 本文是Sun官方以Blog形式发布的Java核心技术窍门(JavaCoreTechTip)中的一个。本文主要介绍了Callable及其相关接口和类的使用,篇幅不长且易于理解,故翻译在了此处,相信对于准备或刚接触java.util.concurrent的朋友会有所帮助。(2008.05.31最后更新)

    自从Java平台的最开始,Runnable接口就已存在了。它允许你定义一个可由线程完成的任务。如大多数人所已知的那样,它只提供了一个run方法,该方法既不接受任何参数,也不返回任何值。如果你需要从一个未完成的任务中返回一个值,你就必须在该接口之外使用一个方法去等待该任务完成时通报的某种消息。例如,下面的示例就是你在这种情景下可能做的事情:
    Runnable runnable = ...;
    Thread t = new Thread(runnable);
    t.start();
    t.join();
    String value = someMethodtoGetSavedValue()
严格来说,上述代码并无错误,但现在可用不同的方法去做,这要感谢J2SE 5.0引入的Callable接口。不同于Runnable接口拥有run方法,Callable接口提供的是call方法,该方法可以返回一个Object对象,或可返回任何一个在泛型化格式中定义了的特定类型的对象。
    public interface Callable<V> {
       V call() throws Exception;
    }
因为你不可能把Callable对象传到Thread对象去执行,你可换用ExecutorService对象去执行Callable对象。该服务接受Callable对象,并经由submit方法去执行它。
    <T> Future<T> submit(Callable<T> task)
如该方法的定义所示,提交一个Callable对象给ExecutorService会返回一个Future对象。然后,Future的get方法将会阻塞,直到任务完成。
    为了证明这一点,下面的例子为命令行中的每个词都创建一个单独的Callable实例,然后把这些词的长度加起来。各个Callable对象将只是计算它自己的词的长度之和。Futures对象的Set集合将被保存以便从中获得计算用的值。如果需要保持返回值的顺序,则可换用一个List对象。
import java.util.*;
import java.util.concurrent.*;

public class CallableExample {

    public static class WordLengthCallable
            implements Callable {
        private String word;
        public WordLengthCallable(String word) {
            this.word = word;
        }
        public Integer call() {
            return Integer.valueOf(word.length());
        }
    }

    public static void main(String args[]) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Set<Future<Integer>> set = new HashSet<Future&lg;Integer>>();
        for (String word: args) {
            Callable<Integer> callable = new WordLengthCallable(word);
            Future<Integer> future = pool.submit(callable);
            set.add(future);
        }
        int sum = 0;
        for (Future<Integer> future : set) {
            sum += future.get();
        }
        System.out.printf("The sum of lengths is %s%n", sum);
        System.exit(sum);
    }
}
WordLengthCallable保存了每个词并使用该词的长度作为call方法的返回值。这个值可能会花点儿时间去生成,不过在这个例子中,可以立即知道它。 call方法的唯一要求是这个值要在call方法的结尾处返回。当Future的get方法稍后被调用时,如果任务运行得很快的话,Future将会自动得到这个值(如同本例的情况),否则将一直等到该值生成完毕为止。多次调用get方法不会导致任务从该线程返回。因为该程序的目的是计划所有字的长度之和,它不会强令Callable任务结束。如果最后一个任务在前三个任务之前完成,也是没错的。对Future的get方法的第一次调用将只会等待Set中第一个任务结束,而不会阻塞其它的任务分别执行完毕。它只会等待当次线程或任务结束。这个特定的例子使用固定数线程池来产生ExecutorService对象,但其它有效的方法也是可行的。
    关于执行器和线程池用法的更多信息,请见Java Tutorial中Executors一节。SwingWorker类是另一个使用Future的Runnable对象的例子,尽管有些微不同之处。更多信息请见Java Tutorial中Worker Threads and SwingWorker一节

 

蜗壳网:http://www.wokenet.com/

原文地址:http://www.blogjava.net/jiangshachina/archive/2008/05/31/204007.html

 

 

<think>我们被要求展示如何使用Callable接口创建线程。CallableJava 5引入的并发工具,它允许线程返回计算结果并抛出异常。与Runnable相比,Callable的call()方法有返回值,并且可以抛出受检异常。 使用Callable创建线程的步骤: 1. 创建一个实现Callable接口的类,并实现call()方法。 2. 创建该Callable实现类的实例。 3. 使用FutureTask类来包装Callable对象,FutureTask实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。 4. 将FutureTask对象作为Thread的target创建线程并启动。 5. 通过FutureTask的get()方法获取线程执行结束后的返回值(get()方法会阻塞直到线程返回结果)。 另外,从Java 8开始,我们可以使用Lambda表达式简化Callable的创建。 下面我们通过两个示例来展示: 示例1:使用Callable实现类(传统写法) 示例2:使用Lambda表达式(简化写法) 示例1:传统写法 我们将创建一个计算1到100和的Callable线程。 示例2:使用线程池(这也是实际工作中最常用的方式) 由于用户提到“在实际工作中,都是基于线程池使用线程”,所以我们也会展示如何通过线程池提交Callable任务。 注意:FutureTask的get()方法会阻塞当前线程直到任务完成,所以如果任务耗时很长,调用get()可能会导致当前线程阻塞。在实际应用中,我们可以使用Future的isDone()来检查任务是否完成,或者使用带超时参数的get()方法。 参考引用[1]和[2]都提到了Callable使用,特别是引用[2]提供了Callable和Future的使用示例。 下面我们写代码示例:</think>### Java 使用 Callable 创建线程的完整指南 `Callable` 接口是 Java 5 引入的并发工具,它允许线程返回计算结果并抛出异常,比 `Runnable` 更强大。以下是使用 `Callable` 创建线程的详细方法和示例代码: #### 1. **基本实现步骤** ```java import java.util.concurrent.*; public class CallableDemo { public static void main(String[] args) throws Exception { // 1. 创建Callable实现类 Callable<Integer> task = new MyCallable(); // 2. 包装为FutureTask FutureTask<Integer> futureTask = new FutureTask<>(task); // 3. 创建线程并启动 Thread thread = new Thread(futureTask); thread.start(); // 4. 获取结果(阻塞直到计算完成) int result = futureTask.get(); System.out.println("计算结果: " + result); } } // Callable实现类 class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; // 返回计算结果 } } ``` **输出结果**: ``` 计算结果: 5050 ``` **关键点**: - `Callable<V>` 的 `call()` 方法有返回值(`V` 类型) - 必须通过 `FutureTask` 包装才能传递给 `Thread` - `futureTask.get()` 会阻塞当前线程直到结果就绪 #### 2. **Lambda 表达式简化写法(Java 8+)** ```java public class LambdaCallable { public static void main(String[] args) throws Exception { // 直接使用Lambda创建Callable FutureTask<Integer> task = new FutureTask<>(() -> { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; }); new Thread(task).start(); System.out.println("1~100求和: " + task.get()); } } ``` #### 3. **通过线程池执行(推荐方式)** ```java ExecutorService executor = Executors.newSingleThreadExecutor(); // 提交Callable任务 Future<Integer> future = executor.submit(() -> { int product = 1; for (int i = 1; i <= 5; i++) { product *= i; } return product; }); System.out.println("5! = " + future.get()); // 输出120 executor.shutdown(); // 关闭线程池 ``` **优势**: - 自动管理线程生命周期 - 支持批量提交任务 - 资源利用率更高[^1] #### 4. **处理异常和超时** ```java Future<Integer> future = executor.submit(() -> { if (Math.random() > 0.5) { throw new Exception("模拟异常"); } return 42; }); try { // 设置2秒超时 Integer result = future.get(2, TimeUnit.SECONDS); System.out.println("结果: " + result); } catch (TimeoutException e) { System.err.println("计算超时"); } catch (ExecutionException e) { System.err.println("执行异常: " + e.getCause().getMessage()); } ``` #### 关键特性对比 | 特性 | Callable | Runnable | |---------------------|------------------|------------------| | **返回值** | ✅ 有 | ❌ 无 | | **抛出受检异常** | ✅ 支持 | ❌ 不支持 | | **Java 版本** | 5+ | 1+ | | **线程池支持** | 通过`submit()` | 通过`execute()` | **最佳实践**: 1. 需要返回值时优先使用 `Callable` 2. 配合线程池管理资源[^1] 3. 使用超时机制避免永久阻塞 4. 复杂任务分离计算逻辑和线程管理 ### 相关问题 1. `Callable` 和 `Runnable` 在异常处理上有何区别? 2. 如何通过 `CompletableFuture` 实现异步回调? 3. 线程池中 `submit()` 和 `execute()` 方法的区别是什么? 4. 如何处理多个 `Callable` 任务的批量执行和结果汇总? [^1]: Java多线程入门4-实现Callable接口创建线程。在实际工作中,都是基于线程池使用线程,也就是更多的是使用Callable接口的方式。 [^2]: Java使用Callable和Future创建线程操作示例。从Java 8开始,可以直接使用Lamda表达式创建Callable对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值