探討阻塞佇列和執行緒池原始碼
阻塞佇列
非阻塞佇列是一個先進先出的單向佇列(Queue),而BlockingQueue阻塞佇列實際是非阻塞佇列的一個加強,之所以是阻塞佇列最主要的是實現take和put,當阻塞佇列滿時,put會一直阻塞直到拿到資料,或者響應中斷退出;當佇列空時,take元素,佇列也會阻塞直到佇列可用。
阻塞的意思就是此條執行緒會處於等待卡死狀態,解鎖的條件是佇列中有另一條執行緒存入或取出資料了,就會解鎖,就相當於佇列是倉庫,倉庫沒有貨了就生產,有貨就能消費,鎖條件是notFull和notEmpty。
Throws exception | Special value | Blocks | Times 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例項實現獲取返回值
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);
相關文章
- 原始碼剖析ThreadPoolExecutor執行緒池及阻塞佇列原始碼thread執行緒佇列
- java多執行緒:執行緒池原理、阻塞佇列Java執行緒佇列
- 執行緒池的阻塞佇列的理解執行緒佇列
- 多執行緒程式設計-分析阻塞佇列的原始碼實現執行緒程式設計佇列原始碼
- java-執行緒池佇列飽和策略Java執行緒佇列
- java執行緒池-工作佇列workQueueJava執行緒佇列
- 執行緒池與工作佇列(轉)執行緒佇列
- Java執行緒(篇外篇):阻塞佇列BlockingQueueJava執行緒佇列BloC
- 執行緒池的建立和使用,執行緒池原始碼初探(篇一)執行緒原始碼
- Java BlockingQueue 阻塞佇列[用於多執行緒]JavaBloC佇列執行緒
- Java阻塞佇列執行緒集控制的實現Java佇列執行緒
- 深入淺出Java多執行緒(十三):阻塞佇列Java執行緒佇列
- 執行緒池原始碼探究執行緒原始碼
- 執行緒池原始碼分析執行緒原始碼
- 阻塞佇列 SynchronousQueue 原始碼解析佇列原始碼
- 阻塞佇列 DelayQueue 原始碼解析佇列原始碼
- java自帶執行緒池和佇列詳細講解Java執行緒佇列
- 最全java多執行緒總結3——瞭解阻塞佇列和執行緒安全集合不Java執行緒佇列
- java多執行緒8:阻塞佇列與Fork/Join框架Java執行緒佇列框架
- 執行緒池監控2-監控執行緒池狀態、執行緒數量和佇列任務數量等執行緒佇列
- java 執行緒池執行緒忙碌且阻塞佇列也滿了時給一個拒接的詳細報告Java執行緒佇列
- 執行緒池之ThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- 執行緒池之ScheduledThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- 原始碼|從序列執行緒封閉到物件池、執行緒池原始碼執行緒物件
- JDK執行緒池原始碼研究JDK執行緒原始碼
- dubbo原始碼-執行緒池分析原始碼執行緒
- Java幾種執行緒池及任務佇列Java執行緒佇列
- 阻塞佇列 LinkedTransferQueue 原始碼解析佇列原始碼
- 阻塞佇列 PriorityBlockingQueue 原始碼解析佇列BloC原始碼
- 執行緒池執行模型原始碼全解析執行緒模型原始碼
- Java執行緒的深入探討Java執行緒
- Netty原始碼解析一——執行緒池模型之執行緒池NioEventLoopGroupNetty原始碼執行緒模型OOP
- 執行緒和執行緒池執行緒
- Java執行緒池原始碼及原理Java執行緒原始碼
- java執行緒池原始碼一窺Java執行緒原始碼
- netty原始碼分析-執行緒池Netty原始碼執行緒
- 踩坑 Spring Cloud Hystrix 執行緒池佇列配置SpringCloud執行緒佇列
- C# 使用執行緒池佇列(學習筆記)C#執行緒佇列筆記