JION
join操作的原理是:阻塞当前的线程,直到准备合并的目标线程的执行。在Java中,线程(Thread)的合并流程是:假设线程A调用了线程B的B.join方法,合并B线程。那么,线程A进入阻塞状态,直到B线程执行完成。
(1)join是实例方法,不是静态方法,需要使用线程对象去调用,如thread.join()。
(2)join调用时,不是线程所指向的目标线程阻塞,而是当前线程阻塞。
(3)只有等到当前线程所指向的线程执行完成,或者超时,当前线程才能重新恢复执行。
(4)join有一个问题:被合并的线程没有返回。
Java Future
Runnable的run方法是没有返回值的,正因为如此,Runnable不能用于需要有返回值的应用场景。此时可以使用Callable接口。但Callable接口的实例不能作为Thread线程实例的target来使用;而Runnable接口实例可以作为Thread线程实例的target构造参数,开启一个Thread线程。Java提供了在Callable实例和Thread的target成员之间一个搭桥的类——FutureTask类。
FutureTask类代表一个未来执行的任务,表示新线程所执行的操作。FutureTask类的构造函数的参数为Callable类型,实际上是对Callable类型的二次封装,可以执行Callable的call方法。FutureTask类间接地继承了Runnable接口,从而可以作为Thread实例的target执行目标。FutureTask类的get方法,获取异步结果时,主线程也会被阻塞的。这一点,FutureTask和join也是一样的,它们俩都是异步阻塞模式。如果需要用到获取异步的结果,则需要引入一些额外的框架,如谷歌的Guava。
Guava Future
Guava的异步任务接口ListenableFuture,扩展了Java的Future接口,实现了非阻塞获取异步结果的功能。Guava对Java的异步回调机制,做了以下的增强:
(1)引入了一个新的接口ListenableFuture,继承了Java的Future接口,使得Java的Future异步任务,在Guava中能被监控和获得非阻塞异步执行的结果。
(2)引入了一个新的接口FutureCallback,这是一个独立的新接口。该接口的目的,是在异步任务执行完成后,根据异步结果,完成不同的回调处理,并且可以处理异步结果。
Guava线程池,是对Java线程池的一种装饰。首先创建Java线程池,然后以它作为Guava线程池的参数,再构造一个Guava线程池。
Guava异步回调的流程如下:
第1步:实现Java的Callable接口,创建异步执行逻辑。还有一种情况,如果不需要返回值,异步执行逻辑也可以实现Java的Runnable接口。
第2步:创建Guava线程池。
第3步:将第1步创建的Callable/Runnable异步执行逻辑的实例,通过submit提交到Guava线程池,从而获取ListenableFuture异步任务实例。
第4步:创建FutureCallback回调实例,通过Futures.addCallback将回调实例绑定到ListenableFuture异步任务上。
Java Join、Java Future、Guava Future实践
Java Join | Java Future | Guava Future |
/*主线程合并烧水和清洗线程后,主线程堵塞, 等待完成后再执行;join有三个重载方法,可以设置等待时间*/ | /* 实现Callable的call()方法,可以获取到线程的直接结果。 这里简单的返回了Boolean类型。 根据返回的结果主线程决定下一步的业务处理逻辑。 但是要注意的是,线程的创建需要借助FutureTask */ | /* Guava通过Futures的addCallback,对绑定的结果进行一步处理。处理回调逻辑绑定在FutureCallback中。 */ |
public class JoinDemo { public static final int SLEEP_GAP = 5000; public static String getCurThreadName() { return Thread.currentThread().getName(); } static class HotWaterThread extends Thread { public HotWaterThread() { super("** 烧水-Thread"); } public void run() { try { Logger.info("洗好水壶"); Logger.info("灌上凉水"); Logger.info("放在火上"); //线程睡眠一段时间,代表烧水中 Thread.sleep(SLEEP_GAP); Logger.info("水开了"); } catch (InterruptedException e) { Logger.info(" 发生异常被中断."); } Logger.info(" 运行结束."); } } static class WashThread extends Thread { public WashThread() { super("$$ 清洗-Thread"); } public void run() { try { Logger.info("洗茶壶"); Logger.info("洗茶杯"); Logger.info("拿茶叶"); //线程睡眠一段时间,代表清洗中 Thread.sleep(SLEEP_GAP); Logger.info("洗完了"); } catch (InterruptedException e) { Logger.info(" 发生异常被中断."); } Logger.info(" 运行结束."); } } public static void main(String args[]) { Thread hThread = new HotWaterThread(); Thread wThread = new WashThread(); hThread.start(); wThread.start(); try { // 合并烧水-线程 hThread.join(); // 合并清洗-线程 wThread.join(); Thread.currentThread().setName("主线程"); Logger.info("泡茶喝"); } catch (InterruptedException e) { Logger.info(getCurThreadName() + "发生异常被中断."); } Logger.info(getCurThreadName() + " 运行结束."); } } | public class JavaFutureDemo { public static final int SLEEP_GAP = 5000; public static String getCurThreadName() { return Thread.currentThread().getName(); } static class HotWaterJob implements Callable<Boolean> { @Override public Boolean call() throws Exception { try { Logger.info("洗好水壶"); Logger.info("灌上凉水"); Logger.info("放在火上"); //线程睡眠一段时间,代表烧水中 Thread.sleep(SLEEP_GAP); Logger.info("水开了"); } catch (InterruptedException e) { Logger.info(" 发生异常被中断."); return false; } Logger.info(" 运行结束."); return true; } } static class WashJob implements Callable<Boolean> { @Override public Boolean call() throws Exception { try { Logger.info("洗茶壶"); Logger.info("洗茶杯"); Logger.info("拿茶叶"); //线程睡眠一段时间,代表清洗中 Thread.sleep(SLEEP_GAP); Logger.info("洗完了"); } catch (InterruptedException e) { Logger.info(" 清洗工作 发生异常被中断."); return false; } Logger.info(" 清洗工作 运行结束."); return true; } } public static void drinkTea(boolean waterOk, boolean cupOk) { if (waterOk && cupOk) { Logger.info("泡茶喝"); } else if (!waterOk) { Logger.info("烧水失败,没有茶喝了"); } else if (!cupOk) { Logger.info("杯子洗不了,没有茶喝了"); } } public static void main(String args[]) { Callable<Boolean> hJob = new HotWaterJob(); FutureTask<Boolean> hTask = new FutureTask<>(hJob); Thread hThread = new Thread(hTask, "** 烧水-Thread"); Callable<Boolean> wJob = new WashJob(); FutureTask<Boolean> wTask = new FutureTask<>(wJob); Thread wThread = new Thread(wTask, "$$ 清洗-Thread"); hThread.start(); wThread.start(); Thread.currentThread().setName("主线程"); try { boolean waterOk = hTask.get(); boolean cupOk = wTask.get(); drinkTea(waterOk, cupOk); } catch (InterruptedException e) { Logger.info(getCurThreadName() + "发生异常被中断."); } catch (ExecutionException e) { e.printStackTrace(); } Logger.info(getCurThreadName() + " 运行结束."); } } | public class GuavaFutureDemo { public static final int SLEEP_GAP = 5000; public static String getCurThreadName() { return Thread.currentThread().getName(); } static class HotWaterJob implements Callable<Boolean> { @Override public Boolean call() throws Exception { try { Logger.info("洗好水壶"); Logger.info("灌上凉水"); Logger.info("放在火上"); //线程睡眠一段时间,代表烧水中 Thread.sleep(SLEEP_GAP); Logger.info("水开了"); } catch (InterruptedException e) { Logger.info(" 发生异常被中断."); return false; } Logger.info(" 烧水工作,运行结束."); return true; } } static class WashJob implements Callable<Boolean> { @Override public Boolean call() throws Exception { try { Logger.info("洗茶壶"); Logger.info("洗茶杯"); Logger.info("拿茶叶"); //线程睡眠一段时间,代表清洗中 Thread.sleep(SLEEP_GAP); Logger.info("洗完了"); } catch (InterruptedException e) { Logger.info(" 清洗工作 发生异常被中断."); return false; } Logger.info(" 清洗工作 运行结束."); return true; } } //泡茶线程 static class MainJob implements Runnable { boolean waterOk = false; boolean cupOk = false; int gap = SLEEP_GAP / 10; @Override public void run() { while (true) { try { Thread.sleep(gap); Logger.info("读书中......"); } catch (InterruptedException e) { Logger.info(getCurThreadName() + "发生异常被中断."); } if (waterOk && cupOk) { drinkTea(waterOk, cupOk); } } } public void drinkTea(Boolean wOk, Boolean cOK) { if (wOk && cOK) { Logger.info("泡茶喝,茶喝完"); this.waterOk = false; this.gap = SLEEP_GAP * 100; } else if (!wOk) { Logger.info("烧水失败,没有茶喝了"); } else if (!cOK) { Logger.info("杯子洗不了,没有茶喝了"); } } } public static void main(String args[]) { //新起一个线程,作为泡茶主线程 MainJob mainJob = new MainJob(); Thread mainThread = new Thread(mainJob); mainThread.setName("主线程"); mainThread.start(); //烧水的业务逻辑 Callable<Boolean> hotJob = new HotWaterJob(); //清洗的业务逻辑 Callable<Boolean> washJob = new WashJob(); //创建java 线程池 ExecutorService jPool = Executors.newFixedThreadPool(10); //包装java线程池,构造guava 线程池 ListeningExecutorService gPool = MoreExecutors.listeningDecorator(jPool); //提交烧水的业务逻辑,取到异步任务 ListenableFuture<Boolean> hotFuture = gPool.submit(hotJob); //绑定任务执行完成后的回调,到异步任务 Futures.addCallback(hotFuture, new FutureCallback<Boolean>() { public void onSuccess(Boolean r) { if (r) { mainJob.waterOk = true; } } public void onFailure(Throwable t) { Logger.info("烧水失败,没有茶喝了"); } }); //提交清洗的业务逻辑,取到异步任务 ListenableFuture<Boolean> washFuture = gPool.submit(washJob); //绑定任务执行完成后的回调,到异步任务 Futures.addCallback(washFuture, new FutureCallback<Boolean>() { public void onSuccess(Boolean r) { if (r) { mainJob.cupOk = true; } } public void onFailure(Throwable t) { Logger.info("杯子洗不了,没有茶喝了"); } }); } } |
Guava异步回调和Java FutureTask异步回调的本质不同
- Guava是非阻塞的异步回调,调用线程是不阻塞的,可以继续执行自己的业务逻辑。
- FutureTask是阻塞的异步回调,调用线程是阻塞的,在获取异步结果的过程中,一直阻塞,等待异步线程返回结果。