1. 程式人生 > >java中的 Future詳解(以及ExecutorService中的各種方法)

java中的 Future詳解(以及ExecutorService中的各種方法)

關於這一塊一前一直是一個盲點,看了看原始碼總結一下把。這一塊內容和執行緒池也是息息相關的

執行緒池的頂級介面是Executor介面,裡面只有一個未實現方法是

void execute(Runnable command);

下來是ExecutorService介面,繼承自Executor介面,裡面多 了很多方法,比較重要的幾個方法是

   <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

提交多個任務,並返回與每個任務對應的Futue。也就是說,任務彼此之間不會相互影響,可以通過future跟蹤每一個任務的執行情況,比如是否被取消,是正常完成,還是異常完成。呼叫該方法的執行緒會阻塞,直到tasks全部執行完成(正常完成/異常退出)

如果執行緒在等待invokeAll執行的過程中被中斷,那麼執行緒池就會終止所以正在被執行的任務,並丟擲異常。

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

給定的超時期滿,還沒有完成的任務會被取消,即Future.isCancelled()返回true;在超時期之前,無論是正常完成還是異常終止的任務,Future.isCancelled()返回false。

 <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

提交多個任務,一旦有一個任務完成,就會終止其他任務的執行,如果沒有一個任務完成,那麼就會丟擲異常。

    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

如果在超時之前,所有任務已經都是異常終止,那就沒有必要在等下去了;如果超時之後,仍然有正在執行或等待執行的任務,那麼會丟擲TimeoutException。

 void shutdown();

終止當前執行緒池,但是執行中的任務會執行到結束,等待的任務不被執行

    List<Runnable> shutdownNow();

終止當前執行緒池,執行中的任務立即結束,等待的任務不被執行

boolean isShutdown();

如果呼叫了上面的兩個shutdown方法,就返回true

 boolean isTerminated();

如果在shutdown之後,所以任務也都結束了,執行緒池處於終結狀態,那麼返回true

 <T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);
  Future<?> submit(Runnable task);

上面的三個方法都是線上程池中提交一個任務,包括callable型別或者是runable型別。返回future物件

AbstractExecutorService抽象類實現了ExecutorService,對上面的submit方法也有了實現,其最終還是在呼叫頂級介面Executor中的execute方法,但是AbstractExecutorService並沒有實現execute方法,該方法在他的實現類ThreadPoolExecutor中實現。

從上面可以看到submit的引數型別有兩種分別是runable和callable,但是在AbstractExecutorService中對submit實現上,都是將兩種物件轉化成了FutureTask物件,然後將這個轉化之後的物件傳入execute方法中。

為啥runnable物件和callable物件可以轉化成futureTask物件呢?下面從Future介面說起。這個介面的方法如下:

  1. cancel方法用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。引數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設定true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設定為true,則返回true,若mayInterruptIfRunning設定為false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。

  2. isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。

  3. isDone方法表示任務是否已經完成,若任務完成,則返回true;

  4. get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;

  5. get(long timeout, TimeUnit unit)用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。

之後RunnableFuture介面繼承了Runnable介面和Future介面,重點,繼承了兩個介面,然後FutureTask類是RunnableFuture的實現類。

FutureTask的構造方法有兩個,一個接受Callable物件,另一個接受runnable物件,但是接受了runnable物件之後會呼叫Executors.callable()方法,將這個runnable物件轉化成callable物件,具體的轉化就是通過一個RunnableAdapter類生成一個callabled物件,然後這個callable物件的call方法就是runnable的run方法。那就不用想也明白了,FutureTask中的實現的run方法一定是執行的直接傳進來的callable物件或者轉化來的call able物件的call方法。事實也是這樣。

那這個callable到底是什麼,怎麼這麼牛逼啊,runnable傳進來還要轉化成他。

public interface Callable<V> {
   
    V call() throws Exception;
}

因為call方法執行完可以返回引數,就這麼簡單,而run的返回是void。ok,正點來了,當我們起一個執行緒去做一個任務的時候呼叫run方法是沒有返回值的,如果我們需要的話就只能用callable了。因為call方法是可以返回結果的。

回頭再看submit方法,傳入一個runnable或者callable,開啟一個新的執行緒去執行,然後返回一個future物件,用來對任務進行操作,是不是很牛逼的設計。

這裡上一個例子看看,這個例子就是用執行緒池加上future來實現多執行緒的一個方式。

public class FutureDemo {
    //建立一個容量為1的執行緒池
    static ExecutorService executorService = Executors.newFixedThreadPool(1);
     
    public static void main(String[] args) throws Exception {
        //建立執行緒並提交執行緒,同時獲取一個future物件
        Thread subThread = new Thread(new SubThread());
        Future future = executorService.submit(subThread);
        //主執行緒處理其他工作,讓子執行緒非同步去執行
        mainWork();
        //阻塞,等待子執行緒結束
        future.get();
        System.out.println("Now all thread done!");
        //關閉執行緒池
        executorService.shutdown();
    }
    //主執行緒工作
    private static void mainWork(){
        System.out.println("Main thread start work!");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Main Thread work done!");
    }
    /**
     * 子執行緒類
     * @author fuhg
     */
    private static class SubThread implements Runnable{
        public void run() {
            // TODO Auto-generated method stub
            System.out.println("Sub thread is starting!");
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("Sub thread is stopping!");
        }
    }
}
當然如果要用future實現多執行緒並不是一定要用執行緒池,只是ExecutorService中的submit可以直接返回future物件,如果我們自己定義future物件的話,就可以不用執行緒池來實現。
Callable<Chuju> onlineShopping = new Callable<Chuju>() {

            @Override
            public Chuju call() throws Exception {
                System.out.println("第一步:下單");
                System.out.println("第一步:等待送貨");
                Thread.sleep(5000);  // 模擬送貨時間
                System.out.println("第一步:快遞送到");
                return new Chuju();
            }
            
        };
   FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
   new Thread(task).start();

這裡我們自己定義了futuretask物件,然後直接作為引數傳遞給thread並執行也是ok的,對任務的操作直接用我們自己定義的task就行了。
        
        Callable<String> callable=new Callable<String>() {
			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				System.out.println("start  call");
				Thread.sleep(5000);
				System.out.println("end  call");
				return "hello word";
			}
		};
		
		ExecutorService executorService = Executors.newFixedThreadPool(1);
		   //建立執行緒並提交執行緒,同時獲取一個future物件
        
        Future future = executorService.submit(callable);
        //主執行緒處理其他工作,讓子執行緒非同步去執行
        //mainWork();
        //阻塞,等待子執行緒結束
        try {
			System.out.println(future.get());//獲取子執行緒執行結束之後返回的結果
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        System.out.println("Now all thread done!");//如果呼叫了get但是子執行緒還沒有執行結束,那麼主執行緒就會阻塞,那麼這句話要在子執行緒
                                                  //執行結束之後,主執行緒開始執行才能輸出。

futuretask有一個重要的屬性就是state

private volatile int state; // 注意volatile關鍵字
    /**
     * 在構建FutureTask時設定,同時也表示內部成員callable已成功賦值,
     * 一直到worker thread完成FutureTask中的run();
     */
    private static final int NEW = 0;

    /**
     * woker thread在處理task時設定的中間狀態,處於該狀態時,
     * 說明worker thread正準備設定result.
     */
    private static final int COMPLETING = 1;

    /**
     * 當設定result結果完成後,FutureTask處於該狀態,代表過程結果,
     * 該狀態為最終狀態final state,(正確完成的最終狀態)
     */
    private static final int NORMAL = 2;

    /**
     * 同上,只不過task執行過程出現異常,此時結果設值為exception,
     * 也是final state
     */
    private static final int EXCEPTIONAL = 3;

    /**
     * final state, 表明task被cancel(task還沒有執行就被cancel的狀態).
     */
    private static final int CANCELLED = 4;

    /**
     * 中間狀態,task執行過程中被interrupt時,設定的中間狀態
     */
    private static final int INTERRUPTING = 5;

    /**
     * final state, 中斷完畢的最終狀態,幾種情況,下面具體分析
     */
    private static final int INTERRUPTED = 6;

然後還給出了四種可能的結果

 Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED

futuretask的cancel方法,get方法都會應用到這些狀態。其中get方法會對呼叫執行緒進行阻塞。

暫時就這麼多把,太深層次的我也看不懂了。