前言

随着业务的发展,系统的功能也越来越多,这时候很多业务操作就需要异步执行,提高效率。大多时候,我们都可以采用线程池来实现异步执行的需求,但是,有时候主流程需要等待其他任务执行完以后才能继续执行,又或者是主流程需要知道其他任务执行的结果,这时候,就需要采用线程的合并了。

在讲线程合并之前,我们先来看看平时使用Thread是怎么实现主线程等待其他线程的。

join合并线程

当一个线程需要等待其他线程执行完再继续执行的时候,我们可以使用join或者是CountDownLatch,这里只说join,CountDownLatch前面的文章已经说过,这里不再赘述。

当线程A调用线程B的join方法时,线程A会阻塞,直到线程B执行完以后才继续执行。

下面举个简单例子:下班回家,煮饭、做菜和烧水这些日常操作是怎么用多线程来实现的。下面是具体流程:

主线程需要等待煮饭线程和烧水线程执行完才能继续下面的吃饭喝水操作。下面我们看看代码是怎么实现的

public class JoinDemo {    static class RiceThread extends Thread{        @Override        public void run() {            try {                System.out.println(Thread.currentThread().getName() + "开始煮饭");                Thread.sleep(10 * 1000);                System.out.println(Thread.currentThread().getName() + "饭煮好了");            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    static class WaterThread extends Thread{        @Override        public void run() {            try {                System.out.println(Thread.currentThread().getName() + "开始烧水");                Thread.sleep(5 * 1000);                System.out.println(Thread.currentThread().getName() + "水烧好了");            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args){        try {            System.out.println(Thread.currentThread().getName() + "回到家");            // 启动煮饭线程            RiceThread riceThread = new RiceThread();            riceThread.start();            // 启动烧水线程            WaterThread waterThread = new WaterThread();            waterThread.start();            System.out.println(Thread.currentThread().getName() + "开始做菜");            Thread.sleep(3 * 1000);            System.out.println(Thread.currentThread().getName() + "菜做好了,等着吃饭");            waterThread.join();            riceThread.join();            System.out.println(Thread.currentThread().getName() + "吃饭喝水。。。");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}//输出main回到家Thread-0开始煮饭main开始做菜Thread-1开始烧水main菜做好了,等着吃饭Thread-1水烧好了Thread-0饭煮好了main吃饭喝水。。。

上面例子看出,使用join可以实现主线程等待其他线程完成以后再往下执行,但是这里有个问题,如果其他线程执行出现问题怎么办,这时候主线程是不知道的,因为Thread中的run方法是没有返回值的。那有什么办法可以异步执行,而且可以获取结果的方法吗?可以用Future来解决。

Future

Future是个接口,它有下面几个方法

public interface Future<V> {    boolean cancel(boolean mayInterruptIfRunning);    boolean isCancelled();    boolean isDone();    V get() throws InterruptedException, ExecutionException;    V get(long timeout, TimeUnit unit)        throws InterruptedException, ExecutionException, TimeoutException;}

主要是通过里面的get()方法来获取异步任务的执行结果,这个方法是阻塞的,直到异步任务执行完成。

Future只是个接口,我们不能直接使用它,需要它的实现类来完成,这时候我们不用自己来实现这个类,Java给我们提高了一个现成的实现类-FutureTask。

下面我们来看看FutureTask的构造方法

public FutureTask(Callable<V> callable) {    if (callable == null)        throw new NullPointerException();    this.callable = callable;    this.state = NEW;       }

构造FutureTask需要传入一个Callable对象,那这个Callable又是什么呢?

我们都知道新起一个线程,可以实现Runable接口来实现,这个Callable和Runable接口很类似,它也只有一个方法:

@FunctionalInterfacepublic interface Callable<V> {    V call() throws Exception;}

但是它和Runable的最大不同就在于这个方法是有返回值的,说到这里,大家应该知道了,获取异步执行结果就是通过这个返回值来获取的。

下面还剩最后一个问题,Java中线程只有Thread类,那这个Callable是怎么实现启动新线程的呢?这就需要借助上面说到的FutureTask了,这个类实现了RunnableFuture接口,而这个接口同时继承了Runable和Future两个接口,Runable可以作为参数传入Thread类,这样就把Callable和Thread联系在一起了。这样说着,大家听着可能有点迷糊,下面我们通过代码来重写一下上面的煮饭流程。

public class FutureDemo {    static class RiceThread implements Callable<Boolean> {        @Override        public Boolean call() throws Exception {            try {                System.out.println(Thread.currentThread().getName() + "开始煮饭");                Thread.sleep(10 * 1000);                // 模拟煮饭出现问题                return false;//                System.out.println(Thread.currentThread().getName() + "饭煮好了");//                return true;            } catch (InterruptedException e) {                System.out.println("煮饭出现异常");                return false;            }        }    }    static class WaterThread implements Callable<Boolean> {        @Override        public Boolean call() throws Exception {            try {                System.out.println(Thread.currentThread().getName() + "开始烧水");                Thread.sleep(5 * 1000);                System.out.println(Thread.currentThread().getName() + "水烧好了");                return true;            } catch (InterruptedException e) {                System.out.println("烧水出现异常");                return false;            }        }    }    public static void main(String[] args) {        try {            Callable<Boolean> riceThread = new RiceThread();            FutureTask<Boolean> rickTask = new FutureTask<>(riceThread);            Thread rThread = new Thread(rickTask);            Callable<Boolean> waterThread = new WaterThread();            FutureTask<Boolean> waterTask = new FutureTask<>(waterThread);            Thread wThread = new Thread(waterTask);            System.out.println(Thread.currentThread().getName() + "回到家");            // 启动煮饭线程            rThread.start();            // 启动烧水线程            wThread.start();            System.out.println(Thread.currentThread().getName() + "开始做菜");            Thread.sleep(3 * 1000);            System.out.println(Thread.currentThread().getName() + "菜做好了,等着吃饭");            Boolean wFlag = waterTask.get();            if (!wFlag) {                System.out.println(Thread.currentThread().getName() + "烧水出问题了,没水喝了");                return;            }            Boolean rFlag = rickTask.get();            if (!rFlag) {                System.out.println(Thread.currentThread().getName() + "煮饭出问题了,没饭吃了");                return;            }            System.out.println(Thread.currentThread().getName() + "吃饭喝水。。。");        } catch (Exception e) {            e.printStackTrace();        }    }}//输出main回到家main开始做菜Thread-0开始煮饭Thread-1开始烧水main菜做好了,等着吃饭Thread-1水烧好了main煮饭出问题了,没饭吃了

上面模拟了其他线程返回数据,主线程FutrueTask的get()方法来获取这些数据,然后进行相应的逻辑。

总结

在Java中,线程等待其他线程执行完,可以使用join来实现;线程等待其他线程而且需要获取结果,可以使用FutureTask来实现。但是join()和FutureTask里的get()方法都是阻塞的,那有什么方法可以完成上面需求而且不阻塞的吗?下回再说!