在Java中什么是同步?什么是异步?对于这两个概念我们必须要明确。只有明确这两个概念,才会在明确在什么场景下使用同步以及异步。
在这里我可以形象的举个例子来辨明这两个概念:
@FunctionalInterface public interface Callable<V> { /** * 返回一个任务的结果,或者抛出一个异常(如果不能计算结果) */ V call() throws Exception; }
Callable接口是一个函数式接口,其中call()方法的返回值的类型就是Callable接口的泛型的类型。但是Callable接口怎么和线程扯上关系呢? FutureTask类存在一个构造器:如下所示:
FutureTask(Callable<V> callable)
其中的参数正式Callable接口对象;FutureTask类又是实现接口RunnableFuture接口:
public class FutureTask<V> implements RunnableFuture<V>
接着看:
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
最终的还是继承了Runnable接口和Future接口,为了说明关系,我们画出类图:
package com._thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThread implements Callable<String> { /** * 票的张数为50张,总共100个人买票 */ private int ticket = 50;// 表示票的张数 @Override public String call() throws Exception { for (int i = 0; i < 100; i++) { if (ticket > 0) { System.out.println("买票:ticket = " + this.ticket--); } } return "票卖光了!"; } public static void main(String[] args) throws InterruptedException, ExecutionException { // 创建Callable接口对象 在这里我创建了两个任务对象 Callable<String> callable1 = new CallableThread(); Callable<String> callable2 = new CallableThread(); // 将创建的callable任务对象存储在FutureTask对象中 FutureTask<String> task1=new FutureTask<String>(callable1); FutureTask<String> task2=new FutureTask<String>(callable2); // 启动线程执行任务 new Thread(task1).start(); new Thread(task2).start(); // 上述代码只是执行线程,callable接口是可以产生结果的,futuretask可以获取结果 // 调用get()方法可以阻止当前执行的线程获取结果 System.out.println("线程A的返回结果是:"+task1.get()); System.out.println("线程B的返回结果是:"+task2.get()); } }
这是使用callable接口来创建线程的一种实现过程;好了现在让我们讨论Java异步编程吧!
上面的图可以说明我们的一个买书的过程,在Java中可以视这个操作为同步操作:
package com._thread; public class SlowWorker { public SlowWorker() {} public void doWork() { try { System.out.println("==== 找书, 找书, 找书 ====== "); Thread.sleep(2000); System.out.println("==== OK! ======"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { SlowWorker worker = new SlowWorker(); // 主线程模拟买书的人,doWork()方法模拟书店老板的行为! System.out.println("老板,我要买书" + new java.util.Date()); worker.doWork(); System.out.println("... 老板在找书的过程中我可以干些事情吗!...."); System.out.println("书买到了" + new java.util.Date()); System.exit(0); } }
看运行结果:
老板,我要买书Sun Jan 21 01:49:35 CST 2018
==== 找书, 找书, 找书 ======
==== OK! ======
... 老板在找书的过程中我可以干些事情吗!....
书买到了Sun Jan 21 01:49:37 CST 2018
以上的操作确实是一种同步的操作;主线程运行开始后,调用doWork()方法,而doWork()方法需要休眠2s.但是主线程没有继续执行,而是等待了,大家是不是感觉这样做事很没有效率。换言之,如果你在书店买书,老板找一天,你会持续等待下去吗?
接下来我们谈谈ExecutorService这个接口,它可以表示线程池对象;当线程空闲时,它可以接受一个提交给ExecutorService的callable对象,当线程结束的时候,他会返回一个Future.
package com._thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * 异步编程 * @author gosaint * */ public class AsynchronousWorker { public AsynchronousWorker() {} public void doWork() { try { System.out.println("==== 找书, 找书, 找书 ====== "); Thread.sleep(2000); System.out.println("==== OK! ======"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { SlowWorker worker = new SlowWorker(); // 主线程模拟买书的人,doWork()方法模拟书店老板的行为! System.out.println("老板,我要买书" + new java.util.Date()); // 此时我们创建一个线程池,个数为3 ExecutorService service = Executors.newFixedThreadPool(3); // 此时存在一个线程池对象,线程池对象提交任务,是一个callable接口 Future<String> future = service.submit(new Callable<String>() { @Override public String call() throws Exception { new AsynchronousWorker().doWork(); return null; } }); System.out.println("... 老板在找书的过程中我可以干些事情吗!...."); System.out.println("做爱做的事情!"); try { //调用此方法可以获取我们任务的执行结果,但是会阻止主线程的运行,直到得到一个结果 future.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("书买到了" + new java.util.Date()); System.exit(0); } }
运行结果如下:
老板,我要买书Sun Jan 21 02:16:13 CST 2018 ... 老板在找书的过程中我可以干些事情吗!.... 做爱做的事情! ==== 找书, 找书, 找书 ====== ==== OK! ====== 书买到了Sun Jan 21 02:16:15 CST 2018
主线程开始运行,接着我们向ExecutorService提交了一个买书的任务,之后我们在干其他的事情。而最后呢,Future对象从ExecutorService获取到了执行的结果。我们调用get()方法获取到了执行的结果;倘若我们没有这个调用,那么还是一个并行计算,那么老板找书的结果不会立马给我们返回的;