java併發編程學習2--Future

【Future的概念

interface Future<V> ,表示異步計算的結果,Future有個get方法而獲取結果只有在計算完成時獲取,不然會一直阻塞直到任務轉入完成狀態,而後會返回結果或者拋出異常。相對於繼承Thread來建立線程方式,使用Runnable可讓你的實現類同時實現多個接口,而相對於Callable及Future,Runnable方法並不返回任務執行結果且不能拋出異常。spring

【interface Future<V> 具備以下方法

public interface Future<V> {
    //取消計算,若是還沒有開始,直接取消再也不運算,若是正在進行:mayInterruptIfRunning等於true就會被中斷。注意一點:只要調用了cancle(),不管任務是否可以執行,再調用get()都會出現cancledException,這是因爲Future.state的狀態被置爲CANCELLED = 4,所致的;
    boolean cancel(boolean mayInterruptIfRunning)
    //判斷計算是否取消。
    boolean isCancelled();
    //判斷計算是否完成,若是計算完成返回true,不然返回false
    boolean isDone();
    //得到異步計算的結果,若是在調用get()的時候結果尚未計算出來,調用線程將被阻塞。
    V get() throws InterruptedException, ExecutionException;
    //得到異步計算的結果,若是在調用get()的時候結果尚未計算出來,調用線程將被阻塞。若是調用超時將會拋出TimeoutException。
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

補充:緩存

- FutureTask:包裝器,能夠將Callable轉換成爲Future與Runnable,它同時實現了兩者的接口。
   - Callable:能夠爲異步方法返回計算結果。

【下面咱們實現一種基於Future的場景

public class Exercise {

       private SimpleApplicationService simpleApplicationService = new SimpleApplicationService();

       @Test
       public void futureTest() throws InterruptedException, ExecutionException {
           long st = System.currentTimeMillis();
           //這裏本是一callable接口實現,我用lambda方便一點
           FutureTask result = new FutureTask<String>(() -> simpleApplicationService.query());
           //記住咱們的任務都與須要開啓一個新的線程
           Thread t = new Thread(result);
           t.start();
           long end = System.currentTimeMillis();
           System.out.println("耗時:" + (end - st) + " 結果:" + result.get());
       }
   }

   public class SimpleApplicationService {
       public String query() throws InterruptedException {
           System.out.println("開始查詢");
           Thread.sleep(2000);
           return "query result";
       }
   }

【反思一些問題:

  1. 每一個異步方法都須要新開啓一個線程這樣很消耗資源。
  2. 每次都要new一個thread挺麻煩的。

【解決方案:使用線程池

  1. 構建一個新的線程是有代價的,由於設計到與操做系統的交互。若是程序中建立了大量的而且生命週期很短的線程,咱們應該使用線程池。
    一個線程池包含不少準備運行的空閒線程,每當run()執行完畢後,線程不會死亡而是回到線程池準備爲下一個請求提供服務。
  2. 另外一個使用線程池的理由是減小併發線程數。建立大量線程會大大下降性能甚至拖垮虛擬機。

【Executor介紹

Executors類有許多靜態方法可建立線程池。例如:併發

  • Executors.newCachedThreadPool():對於一個任務,有空閒線程可用則會當即執行,不然建立一個新的線程。空閒線程存活60s。
  • Executors.newFixedThreadPool(n):構建具備固定大小的線程池,若任務多餘空閒線程數,則將多餘任務放置等待隊列中。
  • Executors.newSingleThreadExecutor():只有一個空閒線程的線程池,任務執行一個接一個;
  • 以上三個方法返回ExecutorService接口的ThreadPoolExecutor對象。

核心(簡介):異步

ThreadPoolExecutoride

public class ThreadPoolExecutor extends AbstractExecutorService {

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue);

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

        //事實上,前面三個構造器都是調用的第四個構造器進行的初始化工做。
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

    }

參數含義:性能

  • corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實現原理有很是大的關係。在建立了線程池後,默認狀況下,線程池中並無任何線程,而是等待有任務到來才建立線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法。從這2個方法的名字就能夠看出,是預建立線程的意思,即在沒有任務到來以前就建立corePoolSize個線程或者一個線程。默認狀況下,在建立了線程池後,線程池中的線程數爲0。當有任務來以後,就會建立一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;
  • maximumPoolSize:線程池最大線程數,這個參數也是一個很是重要的參數,它表示在線程池中最多能建立多少個線程;
  • keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認狀況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起做用。直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,若是一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。可是若是調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起做用,直到線程池中的線程數爲0;
  • unit:參數keepAliveTime的時間單位
  • workQueue:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,通常來講,這裏的阻塞隊列有如下幾種選擇:學習

    - ArrayBlockingQueue
       - LinkedBlockingQueue
       - SynchronousQueue;
  • threadFactory:線程工廠,主要用來建立線程;
  • handler:表示當拒絕處理任務時的策略,有如下四種取值:操作系統

    - ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
              - ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。
              - ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程)
              - ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

使用鏈接池應該作的事:線程

1. 調用Executors的靜態方法建立線程池。
 2. 調用submit(),提交一個Callable對象或者Runnable對象。
 3. 保存好得到的Future<>對象,其中是計算結果(若是提交的是Callable)。
 4. 沒有任何任務再提交的時候使用shutDown()。

public class TaskExercise {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //future + callable
            ExecutorService executor = Executors.newCachedThreadPool();
            CallTask callTask = new CallTask();
            Future<String> taskReturn = executor.submit(callTask);
            System.out.println(taskReturn.get());
            //futureTask
            FutureTask<String> futureTask = new FutureTask<String>(callTask);
            executor.submit(futureTask);
            System.out.println(futureTask.get());
            //runable
            Runnable runTask = new RunTask();
            String result = "xz";
            Future<?> runTaskReturn = executor.submit(runTask,result);
            System.out.println(runTaskReturn.get());

            executor.shutdown();
        }
    }
    class CallTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "task return";
        }
    }
    class RunTask implements Runnable{
        @Override
        public void run() {
            System.out.println("run");
        }
    }

【控制任務組

有時使用執行器更有意義的場景是控制一組相關任務。設計

1. shutdownNow():取消全部任務
2. invokeAny():提交全部對象到一個Callable對象集合,並返回某個已完成的任務結果。例如:若是你願意接受任何一種解決方案的話。
3. 還有其餘一些方法等咱們真正用到時再學習好了~

【補充

spring中實現異步調用:spring爲任務調度與異步方法執行提供了註解支持。經過在方法上設置@Async註解,可以使得方法被異步調用。也就是說調用者會在調用時當即返回,而被調用方法的實際執行是交給Spring的TaskExecutor來完成。若是是有返回值的話記得:接口返回Future<>,實現返回AsyncResult<>。

相關文章
相關標籤/搜索