探討阻塞佇列和執行緒池原始碼

vanchine發表於2020-12-27

阻塞佇列

非阻塞佇列是一個先進先出的單向佇列(Queue),而BlockingQueue阻塞佇列實際是非阻塞佇列的一個加強,之所以是阻塞佇列最主要的是實現take和put,當阻塞佇列滿時,put會一直阻塞直到拿到資料,或者響應中斷退出;當佇列空時,take元素,佇列也會阻塞直到佇列可用。

阻塞的意思就是此條執行緒會處於等待卡死狀態,解鎖的條件是佇列中有另一條執行緒存入或取出資料了,就會解鎖,就相當於佇列是倉庫,倉庫沒有貨了就生產,有貨就能消費,鎖條件是notFull和notEmpty。

Throws exceptionSpecial valueBlocksTimes out
插入方法add(e)offer(e)put(e)offer(e, time, unit)
移除方法remove()poll()take()poll(time, unit)
檢查方法element()peek()

佇列(Queue和Deque)

佇列是一種特殊的線性表,連結串列,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,佇列是一種操作受限制的線性表。

進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。

單向佇列(Queue):先進先出(FIFO),只能從佇列尾插入資料,只能從佇列頭刪除資料.

雙向佇列(Deque):可以從佇列尾/頭插入資料,只能從佇列頭/尾刪除資料.操作頭和尾.

單向佇列Queue結構

private Node first;
private Node last;
int size = 0;

class Node{
    private Node next;
    private Object element;

    public Node(Object element) {
        this.element = element;
    }
    public Node(){}
}

單向佇列操作

public void push(Object element){
    //單向佇列(Queue):先進先出(FIFO),只能從佇列尾插入資料
    size++;
    Node node = new Node(element);
    if (size>1){
        this.last.next = node;
        this.last = node;
    }else if (size == 1){
        this.first = node;
        this.last = node;
    }
}

public void pull(){
    //單向佇列(Queue):先進先出(FIFO),只能從佇列頭刪除資料.
    size--;
    this.first = this.first.next;
    if (size == 1){
        this.last = this.first;
    }
}

常見的阻塞佇列

阻塞佇列:BlockingQueue,多用於建立執行緒池

ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列,只維護一個lock。

執行緒數量的上限與有界任務佇列的狀態有直接關係,如果有界佇列初始容量較大或者沒有達到超負荷的狀態,執行緒數將一直維持在corePoolSize以下,反之當任務佇列已滿時,則會以maximumPoolSize為最大執行緒數上限。

LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列,維護兩個lock。

如果不設定佇列容量,其初始化的預設容量大到可以認為是無界佇列了,在這種情況下maximumPoolSize引數是無效的,佇列中的任務太多,以至於由於無法及時處理導致一直增長,直到最後資源耗盡的問題。

PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。

DelayedWorkQueue:ScheduledThreadPoolExecutor靜態內部類的一個無界延遲阻塞佇列,新增到佇列中的任務,會按照任務的延時時間進行排序,延時時間少的任務首先被執行

SynchronousQueue:無儲存空間的阻塞佇列。

1.SynchronousQueue是一個雙棧雙佇列演算法,無空間的佇列,每執行一個插入操作就會阻塞,需要再執行一個刪除操作才會被喚醒,反之每一個刪除操作也都要等待對應的插入操作。
2.提交的任務不會被儲存,總是會馬上提交執行。如果用於執行任務的執行緒數量小於maximumPoolSize,則嘗試建立新的程式,如果達到maximumPoolSize設定的最大值,則根據設定的handler執行拒絕策略。因此這種方式提交的任務不會被快取起來,而是會被馬上執行,在這種情況下,需要對程式的併發量有個準確的評估,才能設定合適的maximumPoolSize數量,否則很容易就會執行拒絕策略;

LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。

LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。

非阻塞佇列的API

除了阻塞佇列就是非阻塞佇列,非阻塞佇列,就是單純意義上的"非"阻塞佇列,其沒有put和take方法,可以無限制存,且是執行緒安全的,N個使用者同時存也能保證每次存放在隊尾而不亂掉,但是其的size()方法會不定時遍歷,所以很耗時

以ConcurrentLinkedQueue併發佇列為例,多用於訊息佇列,併發非同步處理,如日誌等;add和poll是執行緒安全的,但是其他方法並沒有保證執行緒安全,如判斷isEmpty(),所以在多執行緒併發時還得自己加鎖

ConcurrentLinkedQueue內部就是一個簡單的單連結串列結構,每入隊一個元素就是插入一個Node型別的結點。head指向佇列頭,tail指向佇列尾,通過Unsafe來CAS操作欄位值以及Node物件的欄位值

offer/add新增佇列元素:兩個方法一樣,都是將指定元素插入此佇列的尾部,新增成功返回true;區別在於佇列滿時,add會拋異常,而offer會返回false

poll獲取並移除此佇列的頭,如果此佇列為空,則返回 null

public static void main(String[] args) {
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
    queue.add("1000");
    queue.offer("2000");
    //先進先出
    System.out.println(queue.poll());//1000
    System.out.println(queue.poll());//2000
    System.out.println(queue.poll());//null
}

peek獲取但不移出佇列的頭,如果此佇列為空,則返回 null

public static void main(String[] args) {
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
    queue.add("1000");
    queue.offer("2000");
    //peek獲取但不移除佇列的頭,所以每次peek()都是1000
    System.out.println(queue.peek());//1000
    System.out.println(queue.peek());//1000
    System.out.println(queue.poll());//1000
}

remove從佇列中移除指定元素,已存在元素,會返回true,remove不存在元素,返回false

contains判斷當前佇列是否包含指定元素,如果存在返回true,不存在返回false

public static void main(String[] args) {
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
    queue.add("1000");
    queue.offer("2000");
    System.out.println(queue.remove("1000"));//true
    //1000已經被移除了
    System.out.println(queue.remove("1000"));//false
    System.out.println(queue.contains("1000"));//false
    System.out.println(queue.contains("2000"));//true
}

size佇列的數量,如果此佇列包含的元素數大於 Integer.MAX_VALUE,則只會返回 Integer.MAX_VALUE。由於這些佇列的非同步特性,確定當前的元素數需要進行一次花費 O(n) 時間的遍歷。所以在需要判斷佇列是否為空時,使用peek()!=null,不要用 queue.size()>0

isEmpty判斷當前佇列是否為null

**toArray(T[] a)**轉為指定陣列,按照頭->尾的順序返回

ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
queue.add("1000");
queue.offer("2000");
queue.offer("3000");
Object[] array = queue.toArray();
for (Object o : array){
    System.out.println(o);1000->2000->3000
}

iterator獲取按照頭到尾的順序遍歷的迭代器

ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
queue.add("1000");
queue.offer("2000");
Iterator<Object> iterator = queue.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

佇列應用的示例:

    //建立併發佇列
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();

    //提供100000份需要消耗的食物,並放入佇列
    int productionNum = 10000;
    for(int i = 1;i<10001;i++){
        queue.offer("食物編號:"+i);
    }

    ThreadPoolExecutor executorService = (ThreadPoolExecutor)Executors.newFixedThreadPool(100);
    for (int i =0;i<10;i++){
        //submit()有返回值,而execute()沒有
        Future<?> future = executorService.submit(new Runnable() {
            @Override
            public void run() {
                while (!queue.isEmpty()) {
                    System.out.println("當前執行緒為:" + Thread.currentThread().getName() + ":" + queue.poll());
                }
            }
        });
    }

ArrayBlockQueue/LinkedBlockQueue

ArrayBlockingQueue基於陣列不會產生或銷燬任何額外的物件例項,LinkedBlockingQueue基於連結串列會生成額外的Node物件會有會記憶體溢位的風險。但是常用的其實還是LinkedBlockingQueue,使用兩套鎖,實現生產和消費操作的並行,單個鎖只能保證生產者和消費者只能每次操作一次生產或者消費,而雙鎖可以使得生產者在佇列滿時不斷的向佇列尾部新增node,消費者不斷從head獲取Node,從而吞吐效率更高

//Array可以選擇公平鎖和非公平鎖,而Linked兩把鎖都是非公平鎖
BlockingQueue<String> arrayQueue = new ArrayBlockingQueue<>(100,true);
BlockingQueue<String> linkedQueue = new LinkedBlockingQueue<>(100);
//阻塞的方法只有take和put
queue.take();
queue.put("");

ArrayBlockingQueue原始碼:

//陣列 
final Object[] items;
//獲取資料的索引,主要用於take,poll,peek,remove方法
int takeIndex;
//新增資料的索引,主要用於 put, offer, or add 方法
int putIndex;
//佇列元素的個數
int count;
final ReentrantLock lock;
//notEmpty條件物件,用於通知take方法佇列已有元素,可執行獲取操作
private final Condition notEmpty;
//notFull條件物件,用於通知put方法佇列未滿,可執行新增操作
private final Condition notFull;

put方法

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //可中斷加鎖
    lock.lockInterruptibly();
    try {
        //陣列滿了,生產條件釋放鎖並阻塞執行緒
        while (count == items.length)
            notFull.await();
        //入隊操作
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
    final Object[] items = this.items;
    //設定當前索引值為put新增的值,初始為0
    items[putIndex] = x;
    //length是固定的,簡單理解就是當條件這個元素之後陣列就滿了
    //那麼當消費者從0開始往後消費,生產者被喚醒從而繼續從0開始繼續新增
    //需要先了解消費的方式:會從頭一直消費到最後一個元素之後又從0開始繼續消費
    if (++putIndex == items.length)
        //消費者從0往後依次消費,生產者在從0開始繼續新增
        putIndex = 0;
    count++;
    //喚醒消費者
    notEmpty.signal();
}

take方法

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        //陣列沒有資源,消費者進入休眠
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

private E dequeue() {
    final Object[] items = this.items;
	//takeIndex初始也為0,從索引0開始消費
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    //take索引自增,當消費完最大的索引值,又從0開始消費
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    //消費之後陣列未滿,所以喚醒生產者繼續生產
    notFull.signal();
    return x;
}

LinkedBlockingQueue原始碼:

//由Node單向連結串列組成的佇列,初始化時建立一個空Node並且設定為head和last
static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}
//佇列容量
private final int capacity;
//佇列長度,因為多個執行緒可以同時生產或消費,需要保證改變值的可見性和原子性
private final AtomicInteger count = new AtomicInteger();
//佇列的頭和尾
transient Node<E> head;
private transient Node<E> last;
// 生產者和消費者維護了兩套鎖
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

put方法,可以實現入列和消費同時進行,但是生產或者消費時只能同時執行一個執行緒

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    //生產鎖
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        //如果滿了,當前生產線待機
        while (count.get() == capacity) {
            notFull.await();
        }
        //入列
        enqueue(node);
        //cas設定count+1,返回值仍是原值
        c = count.getAndIncrement();
        //被消費之後佇列未滿則喚醒佇列
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        //當佇列為不為空時,消費鎖釋放
        signalNotEmpty();
}

private void enqueue(Node<E> node) {
	//當前node設定為last,並設定為上一個節點的next
    last = last.next = node;
}

take方法

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        //佇列為空消費者等待被喚醒
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        //count值減1,返回的還是原值
        c = count.getAndDecrement();
        //當消費佇列不為空消費者被喚醒
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    //佇列滿了,生產者進行signal
    if (c == capacity)
        signalNotFull();
    return x;
}

執行緒池

執行緒池,簡單來說就是一個管理工作執行緒的管理器,可以複用執行緒從而減少頻繁建立和銷燬執行緒,控制併發數;可以建立多個執行緒池用於處理不同的業務

AQS佇列是執行中的執行緒由於獲取資源形成佇列,更改執行緒的wait狀態原地阻塞,直到被喚醒繼續執行執行緒;而執行緒池中的任務佇列雖然也是將Runnable物件加入佇列,但並不存在阻塞,因為這是僅僅只是記憶體物件,通過工作執行緒去執行任務佇列裡Runnable物件的run方法,所有其實不一定要是Runnable物件,只需要統一將要執行的任務通過同一個方法去實現,在工作執行緒執行時再去統一調那個方法即可

提交任務執行,就是工作執行緒去直接調任務的run()或者call()方法

class SimperExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

簡易執行緒池

static List<Runnable> runnables = Lists.newArrayList();
static Thread threadPool1 = new Thread(() -> {
    System.out.println("執行緒物件啟動:" + Thread.currentThread().getName());
    while (true) {
        if (!runnables.isEmpty()){
            Thread runnable = (Thread) runnables.get(0);
            System.out.println("被執行任務名稱:"+runnable.getName());
            runnable.run();
            runnables.remove(0);
        }
    }
}, "threadPool1");

private static void execute(Runnable runnable){
    runnables.add(runnable);
}

public static void main(String[] args) throws InterruptedException {
    threadPool1.start();
    Thread t1 = new Thread(() -> {
        System.out.println("任務物件1:"+Thread.currentThread().getName());
    }, "t1");
    Thread t2 = new Thread(() -> {
        System.out.println("任務物件2:"+Thread.currentThread().getName());
    }, "t2");
    Thread t3 = new Thread(() -> {
        System.out.println("任務物件3:"+Thread.currentThread().getName());
    }, "t3");

    execute(t1);
    execute(t2);
    execute(t3);

}

構建ThreadPoolExecutor

ThreadPoolExecutor的繼承和實現結構

Executor只提供了executor方法不需要獲取結果,ExecutorService提供的submit需要獲取結果(FutureTask),可以進行Exception處理

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue){
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    	}
  • corePoolSize 核心執行緒數,初始化ThreadPoolExecutor的時候,執行緒預設不會立即建立核心執行緒建立,等到有任務提交時才會建立

    //初始化執行緒池物件之後並沒有立即建立核心執行緒
    ThreadPoolExecutor executorService = (ThreadPoolExecutor)Executors.newFixedThreadPool(3);
    System.out.println(executorService.getActiveCount());//0
    //當提交任務之後才會先去建立核心執行緒
    executorService.submit(new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println(executorService.getActiveCount());//1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }));
    
  • maximumPoolSize 最大執行緒數,執行緒池允許建立的最大執行緒數,如果佇列中任務已滿,也就是核心執行緒無法及時處理這些任務,那麼會建立新的執行緒來執行任務。

  • workQueue 任務佇列,BlockingQueue

  • keepAliveTime 除了核心執行緒,空閒執行緒存活時間; 設定allowCoreThreadTimeOut(true)可以回收核心執行緒

  • threadFactory 用於建立執行緒池的工作執行緒,可自定義執行緒建立,執行緒池中執行緒就是通過ThreadPoolExecutor中的ThreadFactory執行緒工廠建立的。那麼通過自定義ThreadFactory,可以按需要對執行緒池中建立的執行緒進行一些特殊的設定,如命名、優先順序等

    //預設的DefaultThreadFactory的簡單實現
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            //執行緒命名
            Thread th = new Thread(r,"pool-1"+"-thread");
            return th;
        }
    };
    
  • rejectedHandler 拒絕策略,用來處理執行緒池"超載"的情況,當執行緒池已滿,但是又有新的任務新增到佇列時需要採取的策略,比如什麼都不處理,拋異常,返回false等,也可以自定義實現,預設提供了4種拒絕策略:AbortPolicy,直接丟擲異常,阻止系統正常工作; CallerRunsPolicy,如果執行緒池的執行緒數量達到上限,會把任務佇列中的任務放在呼叫者執行緒當中執行; DiscardOledestPolicy,會丟棄任務佇列中最早的一個任務,也就是當前任務佇列中最先被新增進去的,馬上要被執行的那個任務,並嘗試再次提交;DiscardPolicy,沒有任何處理,也就是丟棄任務,使用此策略,業務場景中需允許任務的丟失;

    實現RejectedExecutionHandler及rejectedExecution方法

    ---- AbortPolicy 拋異常
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
    
    ---- CallerRunsPolicy 溢位的任務直接執行
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           if (!e.isShutdown()) {
               r.run();
           }
    }
    
    ---- DiscardOldestPolicy 取出workQueue中的firstQueue,再重新提交取出的任務
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
    
    ---- DiscardPolicy 什麼都不做,等於直接拋棄了這個任務
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
    

執行緒池的狀態

執行緒狀態和工作執行緒數

//ctl初始為 RUNNING
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//高3位表示執行緒狀態,低29位表示執行緒數
private static final int COUNT_BITS = 32 - 3;
//000 111....
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState 執行緒狀態
private static final int RUNNING    = -1 << COUNT_BITS;//101
private static final int SHUTDOWN   =  0 << COUNT_BITS;//000
private static final int STOP       =  1 << COUNT_BITS;//001
private static final int TIDYING    =  2 << COUNT_BITS;//010
private static final int TERMINATED =  3 << COUNT_BITS;//011

// ~1=0,0&1=1 低29位全部為0,用於獲取高3位的執行緒狀態
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//低29位全部為1,用於獲取低29位的執行緒數
private static int workerCountOf(int c)  { return c & CAPACITY; }
//new AtomicInteger(ctlOf(RUNNING, 0))
private static int ctlOf(int rs, int wc) { return rs | wc; }

RUNNING 定義為 -1,SHUTDOWN 定義為 0,其他的都比 0 大,所以等於 0 的時候不能提交任務,大於 0 的話,連正在執行的任務也需要中斷。

  • RUNNING:最正常的狀態:接受新的任務,處理等待佇列中的任務
  • SHUTDOWN:不接受新的任務提交,但是會繼續處理等待佇列中的任務
  • STOP:不接受新的任務提交,不再處理等待佇列中的任務,中斷正在執行任務的執行緒
  • TIDYING:所有的任務都銷燬了,workCount 為 0。執行緒池的狀態在轉換為 TIDYING 狀態時,會執行鉤子方法 terminated()
  • TERMINATED:terminated() 方法結束後,執行緒池的狀態就會變成這個

狀態變化的轉換過程:

  • RUNNING -> SHUTDOWN:呼叫 shutdown()
  • (RUNNING or SHUTDOWN) -> STOP:呼叫 shutdownNow()
  • SHUTDOWN -> TIDYING:當任務佇列和執行緒池都清空後,會由 SHUTDOWN 轉換為 TIDYING
  • STOP -> TIDYING:當任務佇列清空後,發生這個轉換
  • TIDYING -> TERMINATED:當 terminated() 方法結束後

submit提交任務

executor只能提交沒有返回值得Runnable任務,submit可以提交Runnable和Callable任務,將提交的任務封裝成RunnableFuture

Future介面

對執行緒池提交一個Callable任務,可以獲得一個Future物件

ExecutorService service = new ThreadPoolExecutor(2,2,100,TimeUnit.SECONDS,new LinkedBlockingQueue<>());

List<Future> list = Lists.newArrayList();
for (int i = 0; i < 10; i++) {
    Callable<String> c1 = new Callable<String>() {
        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(10);
            return UUID.randomUUID().toString().replace("-", "");
        }
    };
    //提交callable任務
    Future<String> future = service.submit(c1);
    list.add(future);
}

//阻塞,直到獲取所有future值
list.forEach(future -> {
    try {
        System.out.println(future.get());
    } catch (Exception e) {
        e.printStackTrace();
    }
});
TimeUnit.SECONDS.sleep(3);

實現有返回值的callable介面

//方式一:提交一個Task任務,submit(Callable<T> task)獲取future返回值
ExecutorService service = Executors.newFixedThreadPool(1);
Callable<String> c1 = new Callable<String>() {
    @Override
    public String call() throws Exception {
        System.out.println(111);
        TimeUnit.SECONDS.sleep(10);
        return "a1111";
    }
};
Future<String> future = service.submit(c1);
System.out.println(futureTask.get());

//方式二:封裝為一個FutureTask並啟動執行緒執行,通過FutureTask獲取返回值
Callable<String> c1 = new Callable<String>() {
    @Override
    public String call() throws Exception {
        System.out.println(111);
        TimeUnit.SECONDS.sleep(10);
        return "a1111";
    }
};
FutureTask<String> futureTask = new FutureTask<>(c1);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());

FutureTask

FutureTask 間接實現了 Runnable 和Future介面,所以FutureTask即可以作為一個Runnable任務,也可以作為一個Callable例項實現獲取返回值

img

Runnable 的 run() 方法沒有返回值,第二個引數將會放到 Future 中,作為返回值,通過這兩個引數,將其包裝成 Callable

---- ExecutorService
//提交一個Runnable任務肯定不會有返回值
Future<?> submit(Runnable task);
//兩個引數封裝為Callable
<T> Future<T> submit(Runnable task, T result);
// 提交一個 Callable 任務
<T> Future<T> submit(Callable<T> task);

---- 演示
Callable<String> callable = new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "111";
    }
};

Runnable runnable = new Runnable() {
    @Override
    public void run() {
    }
};

ExecutorService service = Executors.newFixedThreadPool(1);
Future<String> future = service.submit(callable);
Future<?> future1 = service.submit(runnable);
Future<String> run = service.submit(runnable, "run");
System.out.println("future:"+future.get());//111
System.out.println("future1:"+future1.get());//null
System.out.println("run:"+run.get());//run

newTaskFor

將Callable封裝成 FutureTask 提交到執行緒池中執行,Runnable通過RunnableAdapter介面卡封裝成一個Callable

---- 提交任務
public <T> Future<T> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, null);
    //交給執行器執行,execute 方法由具體的子類來實現
    execute(ftask);
    return ftask;
}

//newTaskFor
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

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

public static <T> Callable<T> callable(Runnable task, T result) {
    return new RunnableAdapter<T>(task, result);
}
//Executors,最終發現是通過RunnableAdapter介面卡將Runnable封裝為一個Callable
static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
    	//調run方法,返回result
        task.run();
        return result;
    }
}

run/get

考慮回撥callable時,FutureTask 封裝的任務物件最終是通過工作執行緒執行run()方法處理任務,而FutureTask 的run方法則呼叫了callable的call方法,並將result設值到outcome

//封裝的callable
private Callable<V> callable;
//callable的回撥值,通過get方法獲取
private Object outcome;
//CAS防止callable被執行多次
private volatile Thread runner;
//維護了一個單向連結串列 waiters , 在執行 get 的時候會向其中新增節點
private volatile WaitNode waiters;

//run方法,執行緒池工作執行緒最終通過runWork()調run方法
public void run() {
    // 1. 狀態如果不是NEW,說明任務或者已經執行過,或者已經被取消,直接返回
    // 2. 狀態如果是,則把當前執行執行緒儲存在runner欄位中
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                // 執行call()方法
                result = c.call();
                ran = true;
            } 
            if (ran)
                //執行成功,outcome = result
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        // 如果任務被中斷,執行中斷處理
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    //COMPLETING狀態是任務是否執行完成的臨界狀態,進行阻塞直到任務執行結束
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

private V report(int s) throws ExecutionException {
    Object x = outcome;
    //當任務被執行完返回outcome
    if (s == NORMAL)
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

awaitDone中,get()形成一個阻塞佇列

future可能會有多個執行緒去獲取

Callable<String> callable = new Callable<String>() {
    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        return "111";
    }
};

ExecutorService service = Executors.newFixedThreadPool(1);
Future<String> future = service.submit(callable);

new Thread(()->{
    try {
        System.out.println(Thread.currentThread().getName()+":"+future.get());
    } catch (Exception e) {
        e.printStackTrace();
    }
}).start();
new Thread(()->{
    try {
        System.out.println(Thread.currentThread().getName()+":"+future.get());
    } catch (Exception e) {
        e.printStackTrace();
    }
}).start();

當多個執行緒訪問get方法時,會阻塞形成waiters佇列,由Node維護

FutureTask 執行活動圖

execute 方法

submit方法只是在executor方法中增加一個get獲取Callable返回值功能

真正的開啟執行緒執行任務

public void execute(Runnable command) {
    // 表示 “執行緒池狀態” 和 “執行緒數” 的整數
    int c = ctl.get();
    // 如果當前執行緒數少於核心執行緒數,那麼直接新增一個 worker 來執行任務,
    if (workerCountOf(c) < corePoolSize) {
        // addWorker(command, true)返回 false 代表執行緒池不允許提交任務
        if (addWorker(command, true))
            return;
    }

    //判斷c < SHUTDOWN,只有RUNNING,offer新增任務到任務佇列
    if (isRunning(c) && workQueue.offer(command)) {
		//防止併發更改了執行緒池狀態
        int recheck = ctl.get();
		//防止併發更改了執行緒池狀態
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果執行緒池還是 RUNNING 的,並且執行緒數為 0,那麼開啟新的執行緒
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果 workQueue 佇列滿了,以 maximumPoolSize 為界建立新的 worker
    else if (!addWorker(command, false))
        //執行初始化執行緒池中配置的reject策略
        reject(command);
}

addWorker

addWorker()建立執行緒並執行工作執行緒處理任務,core:是否為核心執行緒

private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
		//基本的校驗,執行緒池狀態不能大於SHUTDOWN
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
            return false;
        for (;;) {
            //工作執行緒數
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                //execute方法已經作了判斷,這裡主要進行check
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                //工作執行緒+1,退出retry迴圈繼續往下執行
                break retry;
            //CAS失敗了,重新獲取c判斷執行緒池狀態
            c = ctl.get();
            if (runStateOf(c) != rs)
                //狀態一致則重新執行內迴圈,否則執行retry迴圈
                continue retry;
        }
    }
	//建立的worker工作執行緒是否已經啟動
    boolean workerStarted = false;
    //建立的worker是否新增到工作佇列中 HashSet<Worker> workers
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        //ThreadFactory封裝之後的工作執行緒
        final Thread t = w.thread;
        if (t != null) {
            // 這個是整個執行緒池的全域性鎖,關閉執行緒池需要這個鎖,持有鎖執行緒池不會被關閉
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                //執行緒池狀態條件判斷,小於SHUTDOWN只有RUNNING
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    //SHUTDOWN狀態時,無法新增任務
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    //記錄執行緒池最大值
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();//啟動工作執行緒
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

Worker

Worker則是執行緒工作例項,封裝了工作執行緒thread,即將被執行的任務firstTask

//封裝worker後的工作執行緒
final Thread thread;
//即將被執行的任務
Runnable firstTask;
//已成功執行完成的任務計數
volatile long completedTasks;

Worker(Runnable firstTask) {
    //Worker繼承AQS,設定的-1就是AQS的SIGNAL狀態
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    //Worker實現Runnable,通過初始化傳參的ThreadFactory封裝當前的Worker
    this.thread = getThreadFactory().newThread(this);
}
//開啟工作執行緒的run方法
public void run() {
    runWorker(this);
}

runWorker

開啟工作執行緒,runWorker處理任務

final void runWorker(Worker w) {
    //執行任務的當前執行緒
    Thread wt = Thread.currentThread();
    //即將處理的任務
    Runnable task = w.firstTask;
    //worker中firstTask置空
    w.firstTask = null;
    //new Worker()是state==-1,呼叫tryRelease()將state置為0, 而interruptIfStarted()中只有state>=0才允許呼叫中斷
    w.unlock(); // allow interrupts
    //異常導致的進入finally,那麼completedAbruptly==true就是突然完成的
    boolean completedAbruptly = true;
    try {
        //工作執行緒只會從firstTask或workQueue中獲取任務
        while (task != null || (task = getTask()) != null) {
            //不是為了防止併發執行任務,在shutdown()時不終止正在執行的worker
            w.lock();
            //確保只有線上程stoping時,才會被設定中斷標示,否則清除中斷標示
            if (...)
                wt.interrupt();
            try {
                //鉤子方法,由子類實現處理任務之前的操作
                beforeExecute(wt, task);
                try {
                    //執行任務的run方法
                    task.run();
                } finally {
                    //同樣是鉤子方法,catch到的thrown異常
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        //沒有突然的結束
        completedAbruptly = false;
    } finally {
        //處理worker的退出
        processWorkerExit(w, completedAbruptly);
    }
}

1、beforeExecute:執行緒池中任務執行前執行

2、afterExecute:執行緒池中任務執行完畢後執行

3、terminated:執行緒池退出後執行

通過覆寫ThreadPoolExecutor例項的方法

pool = new ThreadPoolExecutor(....) {
    protected void beforeExecute(Thread t,Runnable r) {
        System.out.println("準備執行:"+ ((ThreadTask)r).getTaskName());
    } 
    protected void afterExecute(Runnable r,Throwable t) {
        System.out.println("執行完畢:"+((ThreadTask)r).getTaskName());
    }    
    protected void terminated() {
        System.out.println("執行緒池退出");
    }
};

getTask

getTask()獲取任務,當前工作執行緒處理完firstTask開始從任務佇列處理

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 佇列為空時,工作執行緒-1退出迴圈
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        //allowCoreThreadTimeOut為true則會關閉核心執行緒
        //如果>corePoolSize數則一直會在超時之後關閉執行緒
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		//是否獲取workQueue合理性校驗
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
		
        try {
            //timed為true兩種情況:allowCoreThreadTimeOut為ture
            //或者工作執行緒>corePoolSize
            Runnable r = timed ?
                //如果超時會拋異常,獲取不到也表明任務少,不需要這麼多工作執行緒
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                //獲取成功
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            //不拋異常,繼續迴圈
            timedOut = false;
        }
    }
}

Executors工具生成的執行緒池

//獲取可用cpu核心數
public static final int maxThreads = Runtime.getRuntime().availableProcessors();
//Executors工具初始化執行緒池
public static ExecutorService executor =  Executors.newFixedThreadPool(maxThreads);
//分配任務
Future<String> future = executor.submit(callable);
//關閉執行緒池
executorService.shutdown();

生成一個固定大小的執行緒池

最大執行緒數設定為與核心執行緒數相等,此時 keepAliveTime 設定為 0(因為這裡它是沒用的,即使不為 0,執行緒池預設也不會回收 corePoolSize 內的執行緒),任務佇列採用 LinkedBlockingQueue,無界佇列。

每提交一個任務都建立一個 worker,當 worker 的數量達到 nThreads 後,不再建立新的執行緒,而是把任務提交到 LinkedBlockingQueue 中,而且之後執行緒數始終為 nThreads。

//生成一個固定大小的執行緒池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

生成只有一個執行緒的固定執行緒池

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

生成一個需要的時候就建立新的執行緒,同時可以複用之前建立的執行緒的執行緒池

//核心執行緒數為 0,最大執行緒數為 Integer.MAX_VALUE,keepAliveTime 為 60 秒,任務佇列採用 SynchronousQueue
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

建立一個定長執行緒池,支援定時及週期性任務執行,定時執行緒池,schedule定時開啟執行緒池

//最大執行緒數為 Integer.MAX_VALUE,任務佇列採用 DelayedWorkQueue
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

static ScheduledExecutorService scheduledService = new ScheduledThreadPoolExecutor(2);

public static void main(String[] args) {
    long startNow = System.currentTimeMillis();
    //LocalDateTime處理時間
    LocalDateTime dateTime = LocalDateTime.of(2020, 8, 19, 17, 28);
    long time = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant()).getTime();
    System.out.println("time:"+(time-startNow));

```
scheduledService.schedule(new Thread(()->{
    System.out.println("進入schedule處理的時間為:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}),(time-startNow),TimeUnit.MILLISECONDS);

相關文章