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

Java 对多线程编程提供了内置的支持并提供了良好的 API,经过使用 ThreadRunnable 两个基础类,咱们能够很方便的建立一个线程:java

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("线程启动");
        // 耗时操做
        System.out.println("线程结束");
    }
};

Thread thread = new Thread(runnable); // 建立线程,runnable 做为线程要执行的任务(载体)
thread.start(); // 启动线程
thread.join(); // 等待线程执行完毕

{ 题外话开始:编程

经过 Thread 的类声明:
Thread 的类声明segmentfault

咱们能够知道 Thread 本身也实现了 Runnable 接口,Threadrun 方法的实现以下(Thread 启动以后运行的就是 Thread 中的 run 方法):
Thread 中 run 方法的实现多线程

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

题外话结束 }this


Runnable.java 的代码:
Runnable.java 的代码spa

Runnablerun 方法是不带返回值的,那若是咱们须要一个耗时任务在执行完以后给予返回值,应该怎么作呢?线程

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

import java.util.*;

public class RunnableTest {

    public static void main(String[] args) throws Exception {
        System.out.println("使用 Runnable 得到返回结果:");

        List<Thread> workers = new ArrayList<>(10);
        List<AccumRunnable> tasks = new ArrayList<>(10);

        // 新建 10 个线程,每一个线程分别负责累加 1~10, 11~20, ..., 91~100
        for (int i = 0; i < 10; i++) {
            AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10);
            Thread worker = new Thread(task, "慢速累加器线程" + i);

            tasks.add(task);
            workers.add(worker);

            worker.start();
        }

        int total = 0;
        for (int i = 0, s = workers.size(); i < s; i++) {
            workers.get(i).join();  // 等待线程执行完毕
            total += tasks.get(i).getResult();
        }

        System.out.println("\n累加的结果: " + total);
    }

    static final class AccumRunnable implements Runnable {

        private final int begin;
        private final int end;

        private int result;

        public AccumRunnable(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }

        @Override
        public void run() {
            result = 0;
            try {
                for (int i = begin; i <= end; i++) {
                    result += i;
                    Thread.sleep(100);
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace(System.err);
            }
            System.out.printf("(%s) - 运行结束,结果为 %d\n",
                    Thread.currentThread().getName(), result);
        }

        public int getResult() {
            return result;
        }
    }

}

运行结果:
使用 Runnable 得到返回结果code


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

Callable.java 的代码:
Callable.java 的代码

能够看到参数化类型 V 就是返回的值的类型。


可是查看 Thread 的构造方法,咱们发现 Thread 只提供了将 Runnable 做为参数的构造方法,并无使用 Callable<V> 的构造方法 —— 因此引出 FutureTask<V>

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

能够看到它实现了 RunnableFuture<V> 这个接口,咱们再看看 RunnableFuture<V>
RunnableFuture 的接口声明

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

第一个构造方法代表咱们能够经过一个 Callable 去构造一个 FutureTask —— 而 FutureTask 实现了 Runnable 接口,从而能够将该任务传递给 Thread 去运行;

第二个构造方法是经过一个 Runnable 和一个指定的 result 去构造 FutureTask


咱们再来看看 FutureTask<V> 经过 RunnableFuture<V> 实现的第二个接口:Future<V>
(事实上,RunnableFuture<V> 是在 JDK1.6 时添加的类,我猜想在 JDK1.5 时 FutureTask<V> 应该是直接实现的 RunnableFuture<V>,而不是经过 RunnableFuture<V>

Future.java 的源码:
Future.java 的源码

Future<V> 包含的方法有 5 个,本文只关注它两个 get 相关的方法。经过 Java 的 API 文档,咱们能够知道 get 方法是用来返回和 Future 关联的任务的结果(即构造 FutureTask<V> 时使用的 Callable<V> 的结果)。带参数的 get 方法指定一个超时时间,在超时时间内该方法会阻塞当前线程,直到得到结果以后中止阻塞继续运行 —— 若是在给定的超时时间内没有得到结果,那么便抛出 TimeoutException 异常;不带参数的 get 能够理解为超时时间无限大,即一直等待直到得到结果。

Future 每一个方法的详细用法能够参考 Java 多线程(3)

经过以上咱们能够知道,Callable<V>Future<V>FutureTask<V> 这三个类是相辅相成的。以上提到关键类的类关系以下:

以上几个关键类的关系

如今咱们看看 FutureTask 中实现 Runnablerun 方法是怎样的(咱们直接看关键代码):

FutureTask 实现的 run 方法的关键代码

代码的意思很明确,调用构造 FutureTask<V> 时传入的 Callable<V>call 方法,若是正常执行完毕,那么经过 set(result) 设置结果,经过 get() 方法获得的即为这个结果 —— 思路和前面第一种方法是一致的(固然 FutureTask<V> 是更强大和更通用的类);不然即为抛出异常的状况。


使用 FutureTask<V> 的过程以下:
(1)经过一个 Callable<V> 任务或者一个 Runnable(一开始就指定 result)任务构造 FutureTask<V>
(2)将 FutureTask<V> 交给 Thread 去运行;
(3)使用 FutureTask<V>get 方法(或者 Threadjoin 方法)阻塞当前线程直到得到任务的结果。


如今咱们使用 Callable 改写程序:

import java.util.*;
import java.util.concurrent.*;

public class CallableTest {

    public static void main(String[] args) throws Exception {
        System.out.println("使用 Callable 得到返回结果:");
        
        List<FutureTask<Integer>> futureTasks = new ArrayList<>(10);
        // 新建 10 个线程,每一个线程分别负责累加 1~10, 11~20, ..., 91~100
        for (int i = 0; i < 10; i++) {
            AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10);
            FutureTask<Integer> futureTask = new FutureTask<>(task);
            futureTasks.add(futureTask);
            
            Thread worker = new Thread(futureTask, "慢速累加器线程" + i);
            worker.start();
        }

        int total = 0;
        for (FutureTask<Integer> futureTask : futureTasks) {
            total += futureTask.get(); // get() 方法会阻塞直到得到结果
        }

        System.out.println("累加的结果: " + total);
    }

    static final class AccumCallable implements Callable<Integer> {

        private final int begin;
        private final int end;

        public AccumCallable(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }

        @Override
        public Integer call() throws Exception {
            int result = 0;
            for (int i = begin; i <= end; i++) {
                result += i;
                Thread.sleep(100);
            }
            System.out.printf("(%s) - 运行结束,结果为 %d\n",
                    Thread.currentThread().getName(), result);

            return result;
        }
     
    }
    
}

运行结果:
使用 Callable得到返回结果

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

相关文章
相关标签/搜索