如何從線程返回信息——輪詢、回調、Callable

  考慮有這樣一個LiftOff類:java

/**
 * 類LiftOff.java的實現描述:顯示發射以前的倒計時
 * 
 * @author wql 2016年9月21日 下午1:46:46
 */
public class LiftOff implements Runnable {

    public LiftOff(){
        taskCount++;// 計數自增
    }

    private int        countDown = 3;        // 倒計時數字

    private static int taskCount = 0;

    private int        id        = taskCount;

    @Override
    public void run() {

        while (countDown >= 0) {
            System.out.println("線程編號" + id + "--倒計時" + countDown);
            countDown--;
            Thread.yield();
        }
    }
}

  以及一個發射主線程:編程

public class Launch {

    public static void main(String[] args) {
        LiftOff liftOff = new LiftOff();
        Thread t = new Thread(liftOff);
        t.start();
        System.out.println("發射!");
    }
}

  咱們的本意是先顯示倒計時,而後顯示「發射!」,運行結果倒是設計模式

發射!
線程編號0--倒計時3
線程編號0--倒計時2
線程編號0--倒計時1
線程編號0--倒計時0

  由於main()函數也是一個線程,程序可否獲得正確的結果依賴於線程的相對執行速度,而咱們沒法控制這一點。想要使LiftOff線程執行完畢後再繼續執行主線程,比較容易想到的辦法是使用輪詢多線程

/**
 * 類LiftOff.java的實現描述:顯示發射以前的倒計時
 * 
 * @author wql 2016年9月21日 下午1:46:46
 */
public class LiftOff implements Runnable {

    public LiftOff(){
        taskCount++;// 計數自增
    }

    private int        countDown = 3;        // 倒計時數字

    private static int taskCount = 0;

    private int        id        = taskCount;
    
    private boolean isOver = false;

    @Override
    public void run() {

        while (countDown >= 0) {
            System.out.println("線程編號" + id + "--倒計時" + countDown);
            countDown--;
            if(countDown < 0){
                isOver = true;
            }
            Thread.yield();
        }
    }

    public boolean isOver() {
        return isOver;
    }
    
}

  咱們添加了isOver變量,在倒計時結束時將isOver置爲true,主函數中咱們不斷地判斷isOver的狀態,就能夠判斷LiftOff線程是否執行完畢:ide

public class Launch {

    public static void main(String[] args) {
        LiftOff liftOff = new LiftOff();
        Thread t = new Thread(liftOff);
        t.start();
        while (true) {
            if (liftOff.isOver()) {
                System.out.println("發射!");
                break;
            }
        }
    }
}

  執行main(),輸出:函數

線程編號0--倒計時3
線程編號0--倒計時2
線程編號0--倒計時1
線程編號0--倒計時0
發射!

  這個解決方案是可行的,它會以正確的順序給出正確的結果,可是不停地查詢不只浪費性能,而且有可能會因主線程太忙於檢查工做的完成狀況,以致於沒有給具體的工做線程留出時間,更好的方式是使用回調(callback),在線程完成時反過來調用其建立者,告訴其工做已結束:性能

public class LiftOff implements Runnable {
    
    private Launch launch;

    public LiftOff(Launch launch){
        taskCount++;// 計數自增
        this.launch = launch;
    }

    private int        countDown = 3;        // 倒計時數字

    private static int taskCount = 0;

    private int        id        = taskCount;
    
    @Override
    public void run() {

        while (countDown >= 0) {
            System.out.println("線程編號" + id + "--倒計時" + countDown);
            countDown--;
            if(countDown < 0){
                launch.callBack();
            }
            Thread.yield();
        }
    }
}

  主線程代碼:this

public class Launch {
    
    public void callBack(){
        System.out.println("發射!");
    }
    
    public static void main(String[] args) {
        
        Launch launch = new Launch();
        LiftOff liftOff = new LiftOff(launch);
        
        Thread t = new Thread(liftOff);
        t.start(); 
    }
}

  運行結果:spa

線程編號0--倒計時3
線程編號0--倒計時2
線程編號0--倒計時1
線程編號0--倒計時0
發射!

  相比於輪詢機制,回調機制的第一個優勢是不會浪費那麼多的CPU性能,但更重要的優勢是回調更靈活,能夠處理涉及更多線程,對象和類的更復雜的狀況。線程

  例如,若是有多個對象對線程的計算結果感興趣,那麼線程能夠保存一個要回調的對象列表,這些對計算結果感興趣的對象能夠經過調用方法把本身添加到這個對象列表中完成註冊。當線程處理完畢時,線程將回調這些對計算結果感興趣的對象。咱們能夠定義一個新的接口,全部這些類都要實現這個新接口,這個新接口將聲明回調方法。這種機制有一個更通常的名字:觀察者(Observer)設計模式。

Callable

  java5引入了多線程編程的一個新方法,能夠更容易地處理回調。任務能夠實現Callable接口而不是Runnable接口,經過Executor提交任務而且會獲得一個Future,以後能夠向Future請求獲得任務結果:

public class LiftOff implements Callable<String> {
    
    public LiftOff(){
        taskCount++;// 計數自增
    }

    private int        countDown = 3;        // 倒計時數字

    private static int taskCount = 0;

    private int        id        = taskCount;
    
    @Override
    public String call() throws Exception {
        while (countDown >= 0) {
            System.out.println("線程編號" + id + "--倒計時" + countDown);
            countDown--;
        }
        return "線程編號" + id + "--結束";
    }
}

  主函數:

public class Launch {
    
    public static void main(String[] args) {
        
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<String> future = executor.submit(new LiftOff());
        try {
            String s = future.get();
            System.out.println(s);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("發射!");
    }
}

  運行結果:

線程編號0--倒計時3
線程編號0--倒計時2
線程編號0--倒計時1
線程編號0--倒計時0
線程編號0--結束
發射!

  容易使用Executor提交多個任務:

public class Launch {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newCachedThreadPool();
        List<Future<String>> results = new ArrayList<>();
        
        //多線程執行三個任務
        for (int i = 0; i < 3; i++) {
            Future<String> future = executor.submit(new LiftOff());
            results.add(future);
        }
        
        //得到線程處理結果
        for (Future<String> result : results) {
            try {
                String s = result.get();
                System.out.println(s);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        
        //繼續主線程流程
        System.out.println("發射!");
    }
}

  結果:

線程編號0--倒計時3
線程編號0--倒計時2
線程編號0--倒計時1
線程編號0--倒計時0
線程編號2--倒計時3
線程編號2--倒計時2
線程編號1--倒計時3
線程編號1--倒計時2
線程編號1--倒計時1
線程編號1--倒計時0
線程編號2--倒計時1
線程編號2--倒計時0
線程編號0--結束
線程編號1--結束
線程編號2--結束
發射!

  能夠看到,Future的get()方法,若是線程的結果已經準備就緒,會當即獲得這個結果,若是尚未準備好,輪詢線程會阻塞,直到結果準備就緒。

好處

  使用Callable,咱們能夠建立不少不一樣的線程,而後按照須要的順序獲得咱們想要的答案。另外若是有一個很耗時的計算問題,咱們也能夠把計算量分到多個線程中去處理,最後彙總每一個線程的處理結果,從而節省時間。

相關文章
相關標籤/搜索