Java多線程- 自定義Future模式

  • Future 模式釋義 來源於《Java 高併發編程詳解》

假設有個任務需要執行比較長的時間,通常需要等待任務執行結束或者出錯
才能返回結果,在些期間調用者只能陷入阻塞苦苦等待,對此Future設計模式提供了一種憑據式的解決方案。在日常生活中憑據的使用很常見,比如你去服裝店想訂做一套西裝,但又不想將寶貴的時間花在漫長的等待設計師製做西裝的過程中,於是裁縫給你開了一張憑據,上面言道:

  • 定做西裝的單子
  • 取西裝的時間

『帶着這兩個問題來設計一個這樣的Future模式』

假設這個裁縫店的師父是一個做大事且做事規範有序的人,他針對自己的業務類型設計了一個統一的憑據模版(通常用接口或者抽象類來約定),不同的業務有其不同的模版,但都遵循這樣的格式。比如西裝的有西裝的模版,旗袍有旗袍的模版(具體的實現類)。這個模版定義瞭如下這個約定

  • 給客戶的服裝
  • 客戶給了高價錢,必須在某個時間點內得到定製的服裝,超過時間點就不要了,按違約處理。(超時取消任務)
  • 客戶臨時改變主意不做了(取消定做服裝的任務)
  • 服裝是否完成

這個模版用代碼翻譯如下:

package com.example.liuxiaobing.statemodel.future_model;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Create By 劉胡來
 * Create Date 2020/4/23
 * Des: 獲取計算結果
 *
 * future 未來的意思,即將一個耗時操作交給一個線程去執行,從而達到異步的
 * 目的,提交線程在提交任務時和獲得的結果過程中可以進行其他的任務執行,而不至於
 * 傻傻等待結果的返回
 */
public interface Future<T> {

    /**
     * 返回計算後的結果,該方法爲調用者開放,當未獲得他的結果時應該陷入阻塞狀態
     * 線程執行過程中阻塞狀態有可能會被打斷,所以此處我們主動拋出中斷異常
     * @return
     * @throws InterruptedException
     */
    T get() throws InterruptedException;

    /**
     * 帶超時直接拋出超時異常的接口,當在規定的時間內未獲得任務結果返回時,直接將任務取消
     * 使用拋出超時異常來中斷任務,調用者捕獲到超時異常時做自己的邏輯處理
     * 超時的邏輯判斷:
     * 在做任務的線程中判斷任務的耗時時長,如果超過約定的時間則視爲超時
     * 在判斷任務超時後還需要將調用者的阻塞狀態喚醒,這一點很重要
     * @param timeout 超時時間片
     * @return
     * @throws InterruptedException  中斷異常
     * @throws ExecutionException    執行異常
     * @throws TimeoutException     超時異常
     */
    T get(long timeout)
            throws InterruptedException, ExecutionException, TimeoutException;

    T get(TimeUnit unit,long timeout)throws InterruptedException, ExecutionException, TimeoutException;

    /**
     * 取消任務,邏輯判斷依據爲:
     * 將取消任務的標誌置爲真,同時將調用者的阻塞狀態喚醒
     */
    void cancel(boolean cancel);

    /**
     * 判斷任務取消狀態
     * @return
     */
    boolean isCanceled();

    /**
     * 判斷任務是否已經執行完
     * @return
     */
    boolean done();
}

注意每個接口的註釋

  • 具體執行任務的類,由任務線程進行,此處創建了一個線程來進行這個任務,如果爲了高效也可以使用線程池的方式,
    這個線程要處理這麼幾件事:
  • 定做西裝這件具體做的事
  • 判斷這個任務的耗時
  • 監聽客戶的狀態,如是否收到客戶取消定製西裝的任務,如果取消了應立即中斷正在做西裝的任務
  • 西裝完成了之後告訴通知客戶已經做好了,可以過來取西裝了,即將客戶的阻塞狀態喚醒。同時將自己的狀態復原表示可以接收下一個任務
private Date startTime = null;
    private class TaskThread extends Thread{

        public Task<IN, OUT> task;
        private IN input;
        public FutureTask<OUT> future;
        private boolean isCancel;
        public TaskThread(Task<IN, OUT> task,final IN input,FutureTask<OUT> future){
            this.task = task;
            this.input = input;
            this.future = future;
        }

        public void cancelTask(boolean cancel){
            this.isCancel = cancel;
            this.future.cancel(cancel);
        }

        @Override
        public void run() {
            super.run();
            System.out.println("89-----------start work:");

            startTime = new Date();
            OUT result = task.get(input);
            Date currentTime = new Date();
            future.duration = currentTime.getTime() - startTime.getTime();

            future.finish(result);  //喚醒主線程的阻塞,執行後續的邏輯



        }
    }
  • 西裝具體的憑據模板代碼翻譯如下:
package com.example.liuxiaobing.statemodel.future_model;

import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Create By 劉胡來
 * Create Date 2020/4/23
 * Des:
 */
public class FutureTask<T> implements Future<T> {

    private T result;

    private boolean isDone = false;
    public boolean isTimeout = false;
    public boolean isCancel = false;
    public long duration;
    private final Object LOCK = new Object();



    public FutureTask(){
        this.isTimeout = false;
        this.isCancel = false;
        this.duration = 0;
    }

    /**
     * 當任務沒有完成時使用wait進行阻塞
     * @return
     * @throws InterruptedException
     */
    @Override
    public T get() throws InterruptedException {
        synchronized (LOCK){
            while(!isDone){
                LOCK.wait();
            }
            return result;
        }
    }

    @Override
    public T get(long timeout) throws InterruptedException, ExecutionException, TimeoutException {

        synchronized (LOCK){
            System.out.println("43---------thread name:"+Thread.currentThread().getName());
            if(timeout < 0){
                throw new NullPointerException();
            }
            while(!isDone){
                System.out.println("43---------thread name:"+Thread.currentThread().getName() + " 子線程任務還未完成 等待任務線程完成任務 所以 需要處於阻塞狀態");
                LOCK.wait();
            }

            if(isCancel){
                System.out.println("78---------thread name:"+Thread.currentThread().getName() + " 收到任務取消通知,拋出任務中斷異常");
                throw new InterruptedException();
            }
            if(duration > timeout){
                System.out.println("58---------thread name:"+Thread.currentThread().getName() + " 收到超時通知拋出超時異常");
                throw new TimeoutException();
            }

            System.out.println("58---------thread name:"+Thread.currentThread().getName() + " 收到任務線程返回的結果,直接將結果返回");
            return result;
        }
    }

@Override
    public T get(TimeUnit unit, long timeout) throws InterruptedException, ExecutionException, TimeoutException {

        synchronized (LOCK){

            if(timeout < 0){
                throw new IllegalArgumentException("the time is invalid");
            }

            //將時間轉換爲納秒
            long remainingNanos = unit.toNanos(timeout);
            //等待任務將在endNanos 納秒後 超時
            final long endNanos = System.nanoTime() + remainingNanos;

            while(!isDone){
                System.out.println("89---------thread name:"+Thread.currentThread().getName() + " 任務線程任務還未完成 等待任務線程完成任務 所以 需要處於阻塞狀態");

                //超時  直接拋出異常
                if(TimeUnit.NANOSECONDS.toMillis(remainingNanos) <= 0){
                    System.out.println("92---------thread name:"+Thread.currentThread().getName() + " 出現超時,直接拋出超時異常");
                    throw  new TimeoutException("time out");
                }

                //等待remainingNanos  在等待的過程中可能會被中斷,需要重新計算remainingNanos時間
                LOCK.wait(TimeUnit.NANOSECONDS.toMillis(remainingNanos));

                //執行線程中斷時 重新計算時間
                remainingNanos = endNanos - System.nanoTime();

            }

            if(isCancel){
                System.out.println("106---------thread name:"+Thread.currentThread().getName() + " 收到任務取消通知,拋出任務中斷異常");
                throw new InterruptedException();
            }
            System.out.println("109---------thread name:"+Thread.currentThread().getName() + " 收到任務線程返回的結果,直接將結果返回");
            return result;
        }

    }

    /**
     * finish 方法主要用於爲FutureTask 設置結果
     * @param result
     */
    protected void finish(T result){
        synchronized (LOCK){
            if(isDone || isCancel) return;
            this.result = result;
            this.isDone = true;
            LOCK.notifyAll();
        }
    }

    /**
     * 收到取消通知立即喚阻塞的任務,並將取消標誌設爲true
     * @param cancel
     */
    @Override
    public void cancel(boolean cancel){
        synchronized (LOCK){
            if(cancel){
                this.isDone = true;
                this.isCancel = true;
                LOCK.notifyAll();
            }
        }
    }

    @Override
    public boolean isCanceled() {
        return isCancel;
    }


    @Override
    public boolean done() {
        return isDone;
    }
}


  • 代工的任務類型如下:
package com.example.liuxiaobing.statemodel.future_model;

/**
 * Create By 劉胡來
 * Create Date 2020/4/23
 * Des: 主要是提供給調用者實現計算邏輯,接收一個參數並返回一個結果
 */
public interface Task<IN,OUT> {

    /**
     * 給定參數計算返回結果
     * @param input
     * @return
     */
    OUT get(IN input);
}


  • 代工的模版如下:
package com.example.liuxiaobing.statemodel.future_model;

import android.os.Build;
import android.support.annotation.RequiresApi;

import java.util.concurrent.TimeUnit;

/**
 * Create By 劉胡來
 * Create Date 2020/4/23
 * Sensetime@Copyright
 * Des: 用於提交任務
 */
public interface FutureService<IN,OUT> {


    /**
     * 提交不需要返回值的任務,Future.get方法返回的將會是null
     * @param runnable
     * @return
     */
    Future<?> submit(Runnable runnable);

    /**
     * 提交需要返回的任務,其中Task接口代替了Runnable 接口
     * @param task
     * @param input
     * @return
     */
    Future<OUT> submit(Task<IN,OUT>task,IN input);

    /**
     * 提交需要返回的任務,其中Task接口代替了Runnable 接口,支持設置超時拋出超時異常
     * @param task
     * @param input
     * @param timeOut
     * @return
     */
    //Future<OUT> submit(Task<IN,OUT>task, IN input, long timeOut);


    /**
     * 使用靜態方法創建一個FutureService實現
     * @param <T>
     * @param <R>
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    static <T,R> FutureServiceImpl<T,R> newService(){
        return new FutureServiceImpl<>();
    }
}

  • 具體代工模版如下:
package com.example.liuxiaobing.statemodel.future_model;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.Thread.interrupted;

/**
 * Create By 劉胡來
 * Create Date 2020/4/23
 * Des: task任務執行管理類
 */
public class FutureServiceImpl<IN,OUT> implements FutureService<IN,OUT> {

    private TaskThread taskThread;

    public void cancel(boolean cancel){
        if(taskThread != null){
            taskThread.cancelTask(cancel);
            taskThread.interrupt();
        }
    }

    private final static String FUTURE_THREAD_PREFIX = "FUTURE-";

    private final AtomicInteger nextCounter = new AtomicInteger(0);

    private String getNextName(){
        return FUTURE_THREAD_PREFIX + nextCounter.getAndIncrement();
    }

    @Override
    public Future<?> submit(final Runnable runnable) {

        final FutureTask<Void> future = new FutureTask<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                runnable.run();
                future.finish(null);
            }
        },getNextName()).start();

        return future;
    }

    @Override
    public Future<OUT> submit(final Task<IN, OUT> task, final IN input) {
        final FutureTask<OUT> future = new FutureTask<>();
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                OUT result = task.get(input);
//                future.finish(result);
//            }
//        },getNextName()).start();
        if(taskThread == null){
            taskThread = new TaskThread(task,input,future);
            taskThread.setName("TaskThread");
        }
        taskThread.start();

        return future;
    }

    private Date startTime = null;
    private class TaskThread extends Thread{

        public Task<IN, OUT> task;
        private IN input;
        public FutureTask<OUT> future;
        private boolean isCancel;
        public TaskThread(Task<IN, OUT> task,final IN input,FutureTask<OUT> future){
            this.task = task;
            this.input = input;
            this.future = future;
        }

        public void cancelTask(boolean cancel){
            this.isCancel = cancel;
            this.future.cancel(cancel);
        }

        @Override
        public void run() {
            super.run();
            System.out.println("89-----------start work:");

            startTime = new Date();
            OUT result = task.get(input);
            Date currentTime = new Date();
            future.duration = currentTime.getTime() - startTime.getTime();

            future.finish(result);  //喚醒主線程的阻塞,執行後續的邏輯



        }
    }
}

  • 測試

private void testFutureModel(){

        //測試不帶返回值的future
//        FutureService<Void,Void> service = FutureService.newService();
//        Future<?> future = service.submit(()->{
//            try {
//                TimeUnit.SECONDS.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println("185-------i am finish done");
//        });
//
//        try {
//            future.get();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }



        //測試帶返回值的future
//        FutureService<String,Integer> serviceResult = FutureService.newService();
//        Future<Integer> futureResult = serviceResult.submit(new Task<String, Integer>() {
//            @Override
//            public Integer get(String input) {
//                try {
//                    TimeUnit.SECONDS.sleep(4);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println("206-------i am finish done");
//
//                return input.length();
//            }
//        },"Hello");
//
//        try {
//            Integer result = futureResult.get(5 * 1000);
//            System.out.println("211-------i am finish done:"+result);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//            System.out.println("244-------InterruptedException:"+e.toString());
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        } catch (TimeoutException e) {
//            e.printStackTrace();
//            System.out.println("248-------TimeoutException:"+e.toString());
//
//        }
        //測試帶返回值的future 支持超時設置
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = buildFutureTask().get(5 * 1000);
                    System.out.println("239-------i am get result:"+result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("259-------InterruptedException:"+e.toString());
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                    System.out.println("263-------TimeoutException:"+e.toString());
                }
            }
        }).start();


    }


private Future<Integer> buildFutureTask(){

        serviceResult = FutureService.newService();
        futureResult = serviceResult.submit(new Task<String, Integer>() {
            @Override
            public Integer get(String input) {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("273-------InterruptedException");
                }
                System.out.println("276-------i am finish done");

                return input.length();
            }


        },"Hello");
        return futureResult;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章