二. 執行緒管理之執行緒池

jasonhww發表於2019-01-08

不忘初心 砥礪前行, Tomorrow Is Another Day !

相關文章

本文概要:

  1. 認識Executor與ExecutorService
  2. 理解Future與FuturTask
  3. ThreadPoolExecutor介紹
  4. Executors工廠類

執行緒池的優點:

  • 重用執行緒,避免不必要的物件建立和銷燬.
  • 可有效控制最大併發執行緒數,提高系統資源的使用率.避免因執行緒過多強佔系統資源導致阻塞.

一. 認識Executor與ExecutorService

  • 認識Executor

Executor作為執行緒池的頂級介面. 在Java的設計中,Runnable負責任務的提交. Executor負責任務的執行.將任務進行了解耦.

public interface Executor {

    void execute(Runnable command);//執行已提交的 Runnable 任務物件
}
複製程式碼
  • 認識ExecutorService

ExecutorService介面繼承了Executor介面,定義了一些生命週期的方法

public interface ExecutorService extends Executor {

   
    void shutdown();//順次地關閉ExecutorService,停止接收新的任務,等待所有已經提交的任務執行完畢之後,關閉ExecutorService


    List<Runnable> shutdownNow();//阻止等待任務啟動並試圖停止當前正在執行的任務,停止接收新的任務,返回處於等待的任務列表


    boolean isShutdown();//判斷執行緒池是否已經關閉

    boolean isTerminated();//如果關閉後所有任務都已完成,則返回 true。注意,除非首先呼叫 shutdown 或 shutdownNow,否則 isTerminated 永不為 true。

    
    boolean awaitTermination(long timeout, TimeUnit unit)//等待(阻塞)直到關閉或最長等待時間或發生中斷,timeout - 最長等待時間 ,unit - timeout 引數的時間單位  如果此執行程式終止,則返回 true;如果終止前超時期滿,則返回 false 

 
    <T> Future<T> submit(Callable<T> task);//提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。該 Future 的 get 方法在成功完成時將會返回該任務的結果。


    <T> Future<T> submit(Runnable task, T result);//提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功完成時將會返回給定的結果。

 
    Future<?> submit(Runnable task);//提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功 完成時將會返回 null


    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)//執行給定的任務,當所有任務完成時,返回保持任務狀態和結果的 Future 列表。返回列表的所有元素的 Future.isDone() 為 true。
        throws InterruptedException;


    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)//執行給定的任務,當所有任務完成時,返回保持任務狀態和結果的 Future 列表。返回列表的所有元素的 Future.isDone() 為 true。
        throws InterruptedException;


    <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;
}
複製程式碼

在ExecutorService中運用到了Future相關知識,下面對Futue做一個簡單瞭解.

二. 理解Future與FutureTask

  • 理解Future

Future簡單理解就是對非同步任務的統計類,包含進行取消、查詢是否完成、獲取結果等操作.

Future類位於java.util.concurrent包下,對應原始碼.

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    //表示如果在任務完成前被取消成功,則返回true
    boolean isCancelled();
    ////表示任務執行結束,無論是正常結束/中斷/發生異常,都返回true
    boolean isDone();
    //用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回
    V get() throws InterruptedException, ExecutionException;
    //用來獲取執行結果,有超時機制,如果阻塞時間超過了指定時間,會丟擲異常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
複製程式碼
  • 理解FutureTask

FutureTask既實現了Future介面,又實現了Runnable介面.

對應原始碼

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

//構造方法
public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
}

public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //回撥callable的Call方法,獲取非同步任務返回值.
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            //...省略部分程式碼
        }
    }
複製程式碼

所以我們可以通過Runnable介面實現執行緒,又可以通過Future介面獲取執行緒執行完後的結果.

使用示例 上一篇文章Thread基礎中已經使用過Future了,這裡直接換成FutureTask的使用.

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("子執行緒正在幹活");
        Thread.sleep(3000);
        return "實現Callable介面,重寫Call方法";
    }

    public static void main(String[] args) throws Exception {
        useFutureTask();
    }

    private static void  useFutureTask() throws InterruptedException, ExecutionException {
        MyCallable myCallable = new MyCallable();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //定義一個Task,再提交任務.
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        executorService.submit(futureTask);

        executorService.shutdown();

        Thread.sleep(1000);//模擬正在幹活
        System.out.println("主執行緒正在幹活");
        //阻塞當前執行緒,等待返回結果.
        System.out.println("等待返回結果:" + futureTask.get());
        System.out.println("主執行緒所有的活都幹完了");
    }
}

//呼叫輸出
子執行緒正在幹活
主執行緒正在幹活
等待返回結果:實現Callable介面,重寫Call方法
主執行緒所有的活都幹完了
複製程式碼

說到這裡,可能有些人和我一樣迷糊,ExecutorService可以通過submit和execute進行執行任務.那麼這兩者區別是啥.我們來簡單的總結一下.

對應原始碼

//submit方法
<T> Future<T> submit(Callable<T> task);

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

Future<?> submit(Runnable task);

//execute方法
void execute(Runnable command);
複製程式碼

從原始碼對比可以知道.

  • 接收的引數不一樣.execute僅能接收Runnable型別.
  • submit()有返回值,而execute()沒有
  • submit()可以進行Exception處理.

通過總結會發現這其實就與前面介紹的實現Runable與Callable介面的特點類似.

三. ThreadPoolExecutor介紹

所有執行緒池都是通過ThreadPoolExecutor來建立.

對應原始碼

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
複製程式碼

引數解析

  • corePoolSize : 核心執行緒數
  • maximumPoolSize : 最大執行緒數
  • keepAliveTime : 非核心執行緒閒置時的超時時間.如果設定了allowCoreThreadTimeOut為true,那麼也將作用於核心執行緒.
  • unit : 時間單位
  • workQueue : 任務佇列.存放Runnable任務.

另外還有2個引數,一般我們不需要去手動設定.

  • ThreadFactory : 執行緒工廠,建立執行緒用.
  • RejectedExecutionHandler : 任務佇列已滿或者無法成功執行任務時,會呼叫此handler的rejectedExecution方法丟擲異常通知呼叫者,俗稱飽和策略.

理解ThreadPoolExecutor處理的流程

二. 執行緒管理之執行緒池
執行緒池工作流程
  1. 在提交任務時,檢查是否達到核心執行緒數量.
    • 未達到,則啟動核心執行緒去執行任務.
    • 已達到,進行下一步.
  2. 檢查任務佇列是否已滿.
    • 佇列未滿,加入任務佇列.
    • 佇列已滿,進行下一步.
  3. 檢查是否到達最大執行緒數.
    • 未達到,啟動非核心執行緒去執行任務.
    • 已達到,執行飽和策略.
  • 一旦有執行緒處於閒置時,就會去佇列中獲取任務執行.

四. Executors工廠類

通過Executors提供四種執行緒池,newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool、ewCachedThreadPool.

  • FixedThreadPool

可重用固定執行緒數的執行緒池.

對應原始碼

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

複製程式碼
  • 只有核心執行緒.數量固定
  • 無超時機制,佇列大小無限制.

使用示例

private static void executeFixedThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行緒:" + Thread.currentThread().getName());
                }
            });
        }
    }
複製程式碼
  • SingleThreadExecutor

單執行緒的執行緒池

對應原始碼

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
複製程式碼

同FixedThreadPool類似.

  • 固定只有一個核心執行緒. 因此它能確保所有任務在一個執行緒中按順序執行.

使用示例

private static void executeSingleThreadExecutor() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行緒:" + Thread.currentThread().getName());
                }
            });
        }
    }
複製程式碼
  • ScheduledThreadPool

支援定時和週期性任務的執行緒池

對應原始碼

ScheduledExecutorService.java
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

ScheduledThreadPoolExecutor.java    
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
複製程式碼

同FixedThreadPool類似.

  • 核心執行緒數量固定,非核心執行緒不定.

使用示例

private static void executeScheduledThreadPool(int flag) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("後"+System.currentTimeMillis());
//                try {
//                    Thread.sleep(10000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                System.out.println("執行緒:" + Thread.currentThread().getName());

            }
        };
        System.out.println("前"+System.currentTimeMillis());
        switch (flag) {
            case 0:
                //延遲1000毫秒後開始執行
                executorService.schedule(runnable, 1000, TimeUnit.MILLISECONDS);
                break;
            case 1:
                //延遲1000毫秒後開始執行,後面每隔2000毫秒執行一次.強調任務的執行頻率,不受任務執行時間影響,過時不候.
                executorService.scheduleAtFixedRate(runnable, 1000, 2000, TimeUnit.MILLISECONDS);
                break;
            case 2:
                //延遲1000毫秒後開始執行,後面每次延遲3000毫秒執行一次.強調任務執行的間隔.
                executorService.scheduleWithFixedDelay(runnable, 1000, 3000, TimeUnit.MILLISECONDS);

                break;
            default:
                break;
        }
    }

複製程式碼
  • CachedThreadPool

對應原始碼

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
複製程式碼
  • 只有非核心執行緒.
  • 有超時機制,佇列無法儲存,被立即執行.

因此適合大量的耗時較少的任務.

使用示例

private static void executeCachedThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行緒:" + Thread.currentThread().getName());
                }
            });
        }
    }
複製程式碼

關於執行緒池相關就介紹到這裡了.

由於本人技術有限,如有錯誤的地方,麻煩大家給我提出來,本人不勝感激,大家一起學習進步.

參考連結:

相關文章