在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;// 表示票的張數 @Overridepublic 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());// 此時我們創建一個線程池,個數為3ExecutorService service = Executors.newFixedThreadPool(3);// 此時存在一個線程池對象,線程池對象提交任務,是一個callable接口Future<String> future = service.submit(new Callable<String>() {@Overridepublic 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()方法獲取到了執行的結果;倘若我們沒有這個調用,那么還是一個并行計算,那么老板找書的結果不會立馬給我們返回的;