1. 程式人生 > >線程2---異步1

線程2---異步1

lee 分布式系統 import trac 而是 trace pool public log

  在Java中什麽是同步?什麽是異步?對於這兩個概念我們必須要明確。只有明確這兩個概念,才會在明確在什麽場景下使用同步以及異步。

  在這裏我可以形象的舉個例子來辨明這兩個概念:

  1.同步與異步   同步和異步關註的是消息通信機制 (synchronous communication/ asynchronous communication)   所謂同步,就是在發出一個*調用*時,在沒有得到結果之前,該*調用*就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由*調用者*主動等待這個*調用*的結果。   而異步則是相反,*調用*在發出之後,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出後,調用者不會立刻得到結果。而是在*調用*發出後,*被調用者*通過狀態、通知來通知調用者,或通過回調函數處理這個調用。   典型的異步編程模型舉個通俗的例子:你打電話問書店老板有沒有《分布式系統》這本書,   如果是同步通信機制,書店老板會說,你稍等,”我查一下",然後開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。   而異步通信機制,書店老板直接告訴你我查一下啊,查好了打電話給你,然後直接掛電話了(不返回結果)。然後查好了,他會主動打電話給你。   在這裏老板通過“回電”這種方式來回調。   2. 阻塞與非阻塞阻塞和非阻塞關註的是程序在等待調用結果(消息,返回值)時的狀態.阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之後才會返回。非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。還是上面的例子,   你打電話問書店老板有沒有《分布式系統》這本書,你如果是阻塞式調用,你會一直把自己“掛起”,直到得到這本書有沒有的結果,如果是非阻塞式調用,你不管老板有沒有告訴你,你自己先一邊去玩了, 當然你也要偶爾過幾分鐘check一下老板有沒有返回結果。在這裏阻塞與非阻塞與是否同步異步無關。跟老板通過什麽方式回答你結果無關。   在開始解決上述問題之前我們來討論下使用Callbale接口來創建線程。
@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()方法獲取到了執行的結果;倘若我們沒有這個調用,那麽還是一個並行計算,那麽老板找書的結果不會立馬給我們返回的; 

線程2---異步1