在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

Java 多线程(1):获得线程的返回结果

2016-12-16 12:53| 发布者: zhangjf| 查看: 497| 评论: 0

摘要: Java 对多线程编程提供了内置的支持并提供了良好的 API,通过使用 Thread 和 Runnable 两个基础类,我们可以很方便的创建一个线程:Runnable runnable = new Runnable() { @Override public void run() { ...

Java 对多线程编程提供了内置的支持并提供了良好的 API,通过使用 Thread 和 Runnable 两个基础类,我们可以很方便的创建一个线程:

  1. Runnable runnable = new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("线程启动");
  5. // 耗时操作
  6. System.out.println("线程结束");
  7. }
  8. };
  9. Thread thread = new Thread(runnable); // 创建线程,runnable 作为线程要执行的任务(载体)
  10. thread.start(); // 启动线程
  11. thread.join(); // 等待线程执行完毕
复制代码

{ 题外话开始:
通过 Thread 的类声明:

我们可以知道 Thread 自己也实现了 Runnable 接口,Thread 中 run 方法的实现如下:

(target 即构造 Thread 时可传入的 Runnable 对象,不传入即为 null —— 所以继承 Thread 重写其 run 方法也是一种创建线程的方式)

题外话结束 }

Runnable.java 的代码:

  1. public interface Runnable {
  2. public void run();
  3. }
复制代码

Runnable 的 run 方法是不带返回值的,那如果我们需要一个耗时任务在执行完之后给予返回值,应该怎么做呢?

第一种方法:在 Runnable 的实现类中设置一个变量 V,在 run 方法中将其改变为我们期待的结果,然后通过一个 getV() 方法将这个变量返回。

  1. import java.util.*;
  2. public class RunnableTest {
  3. public static void main(String[] args) throws Exception {
  4. System.out.println("使用 Runnable 获得返回结果:");
  5. List<Thread> workers = new ArrayList<>(10);
  6. List<AccumRunnable> tasks = new ArrayList<>(10);
  7. // 新建 10 个线程,每个线程分别负责累加 1~10, 11~20, ..., 91~100
  8. for (int i = 0; i < 10; i++) {
  9. AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10);
  10. Thread worker = new Thread(task, "慢速累加器线程" + i);
  11. tasks.add(task);
  12. workers.add(worker);
  13. worker.start();
  14. }
  15. int total = 0;
  16. for (int i = 0, s = workers.size(); i < s; i++) {
  17. workers.get(i).join(); // 等待线程执行完毕
  18. total += tasks.get(i).getResult();
  19. }
  20. System.out.println("n累加的结果: " + total);
  21. }
  22. static final class AccumRunnable implements Runnable {
  23. private final int begin;
  24. private final int end;
  25. private int result;
  26. public AccumRunnable(int begin, int end) {
  27. this.begin = begin;
  28. this.end = end;
  29. }
  30. @Override
  31. public void run() {
  32. result = 0;
  33. try {
  34. for (int i = begin; i <= end; i++) {
  35. result += i;
  36. Thread.sleep(100);
  37. }
  38. } catch (InterruptedException ex) {
  39. ex.printStackTrace(System.err);
  40. }
  41. System.out.printf("(%s) - 运行结束,结果为 %dn",
  42. Thread.currentThread().getName(), result);
  43. }
  44. public int getResult() {
  45. return result;
  46. }
  47. }
  48. }
复制代码

运行结果:

第二种方法:使用 Callable 和 FutureTask
Callable 是 JDK1.5 时添加的类,为的就是解决 Runnable 的痛点(没有返回值不能抛出异常)。

Callable.java 的代码:

  1. public interface Callable<V> {
  2. V call() throws Exception;
  3. }
复制代码

可以看到参数化类型 V 就是返回的值的类型。
但是查看 Thread 类的构造方法,我们发现 Thread 只提供了将 Runnable 作为参数的构造方法,并没有使用 Callable 的构造方法 —— 所以引出 FutureTask

FutureTask 也是 JDK1.5 时添加的类,查看它的类声明:

可以看到它实现了 RunnableFuture 这个接口,我们再看看 RunnableFuture

可以看到 RunnableFuture 接口继承了 Runnable 接口,那么 RunnableFuture 接口的实现类 FutureTask 必然会去实现 Runnable 接口 —— 所以 FutureTask 可以用来当 Runnable 使用。查看 FutureTask 的构造方法,发现 FutureTask 有两个构造方法:

第一个构造方法表明我们可以通过一个 Callable 去构造一个 FutureTask —— 而 FutureTask 实现了 Runnable 接口,从而可以将该任务传递给 Thread 去运行。

(第二个构造方法是通过一个 Runnable 和一个指定的 result 去构造 FutureTask —— 任务还没有开始之前就指定好 result —— 所以,这个构造方法不常用)

我们再来看看 FutureTask 通过 RunnableFuture 实现的第二个接口:Future
(事实上,RunnableFuture 是在 JDK1.6 时添加的类,我猜测在 JDK1.5 时 FutureTask 应该是直接实现的 Runnable 和 Future,而不是通过 RunnableFuture

Future.java 的代码:

  1. public interface Future<V> {
  2. boolean cancel(boolean mayInterruptIfRunning);
  3. boolean isCancelled();
  4. boolean isDone();
  5. V get() throws InterruptedException, ExecutionException;
  6. V get(long timeout, TimeUnit unit)
  7. throws InterruptedException, ExecutionException, TimeoutException;
  8. }
复制代码

Future 包含的方法有 5 个,本文只关注它两个 get 相关的方法。通过 Java 的 API 文档,我们可以知道 get 方法是用来返回和 Future 关联的任务的结果(即 FutureTask(Callable) 的结果);带参数的 get 方法可以指定一个超时时间,在超时时间内该方法会阻塞当前线程,直到获得结果(之后停止阻塞继续运行) —— 如果在给定的超时时间内没有获得结果,那么便抛出 TimeoutException 异常;不带参数的 get 可以理解为超时时间无限大,即一直等待直到获得结果(或者执行任务被中断或者出错)。

通过以上我们可以知道,Callable、Future、FutureTask 这三个类是相辅相成的。以上提到关键类的类关系如下:

现在我们看看 FutureTask 中实现 Runnable 的 run 方法是怎样的(我们直接看关键代码):

代码的意思很明确,调用构造 FutureTask 时传入的 Callable 的 call 方法,如果正常执行完毕,那么通过 set(result) 设置结果,通过 get() 方法得到的即为这个结果 —— 和前面第一种方法是一致的 —— 当然 FutureTask 是更加强大和通用的类;否则即为抛出异常的情况。

现在我们使用 Callable 改写程序:

  1. import java.util.*;
  2. import java.util.concurrent.*;
  3. public class CallableTest {
  4. public static void main(String[] args) throws Exception {
  5. System.out.println("使用 Callable 获得返回结果:");
  6. List<FutureTask> tasks = new ArrayList<>(10);
  7. // 新建 10 个线程,每个线程分别负责累加 1~10, 11~20, ..., 91~100
  8. for (int i = 0; i < 10; i++) {
  9. AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10);
  10. FutureTask<Integer> futureTask = new FutureTask<>(task);
  11. Thread worker = new Thread(futureTask, "慢速累加器线程" + i);
  12. tasks.add(futureTask);
  13. worker.start();
  14. }
  15. int total = 0;
  16. for (FutureTask<Integer> futureTask : tasks) {
  17. total += futureTask.get(); // get() 方法会阻塞直到获得结果
  18. }
  19. System.out.println("累加的结果: " + total);
  20. }
  21. static final class AccumCallable implements Callable<Integer> {
  22. private final int begin;
  23. private final int end;
  24. public AccumCallable(int begin, int end) {
  25. this.begin = begin;
  26. this.end = end;
  27. }
  28. @Override
  29. public Integer call() throws Exception {
  30. int result = 0;
  31. for (int i = begin; i <= end; i++) {
  32. result += i;
  33. Thread.sleep(100);
  34. }
  35. System.out.printf("(%s) - 运行结束,结果为 %dn",
  36. Thread.currentThread().getName(), result);
  37. return result;
  38. }
  39. }
  40. }
复制代码

运行结果:

可以看到使用 Callable + FutureTask 的程序代码要比 Runnable 的代码更简洁和方便 —— 当需要线程执行完成返回结果时(或者任务需要抛出异常),Callable 是优先于 Runnable 的选择。

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-7 18:59

Copyright 2015-2025 djqfx

返回顶部