Java高并發(fā) -- 線程池
主要是學習慕課網(wǎng)實戰(zhàn)視頻《Java并發(fā)編程入門與高并發(fā)面試》的筆記
在使用線程池后,創(chuàng)建線程變成了從線程池里獲得空閑線程,關(guān)閉線程變成了將線程歸壞給線程池。
JDK有一套Executor框架,大概包括Executor、ExecutorService、AbstractExeccutorService、ThreadPoolExecutor、Executors等成員,位于java.util.concurrent
包下。它們之間的關(guān)系如下:
Executor是頂層的接口,ExecutorService接口繼承了它,AbstrctExecutorService繼承了ExecutorService,ThreadPoolExecutor繼承了AbstrctExecutorService。如果用<——
表示繼承,<--
表示實現(xiàn)接口,它們的關(guān)系可表示如下:
Executor(接口) <—— ExecutorService(接口) <-- AbstrctExecutorService(抽象類) <—— ThreadPoolExecutor(類)
Executors是單獨的一個類,可以看成是“線程池工廠”,它有很多靜態(tài)方法,比如:
- newFixedThreadPool(int nThread)
- newSingleThreadExecutor()
- newCachedThreadPool()
- newSingleThreadScheduledExecutor()
- newScheduledThreadPool(int corePoolSize)
newFixedThreadPool該方法返回一個固定線程數(shù)的線程池。當有新任務(wù)提交時,如果線程池中有空閑線程就立即執(zhí)行,否則會進入任務(wù)隊列中,等到有空閑線程了才能執(zhí)行。
newSingleThreadExecutor,該方法返回只有一個線程的線程池,處理策略和上面一樣。實際上就是上面的參數(shù)指定為1而已。
newCachedThreadPool該方法返回一個可根據(jù)實際情況調(diào)整線程數(shù)的線程池,任務(wù)提交后,如果有空閑線程可以復用,則優(yōu)先復用。若線程池中的線程全部在工作,而此時有新任務(wù),則會創(chuàng)建新的線程來處理任務(wù),所有線程執(zhí)行完后會將線程歸還給線程池。
newScheduledThreadPool返回一個ScheduledExecutorService對象,可以有計劃地執(zhí)行任務(wù),比如在某個延時之后開始執(zhí)行,或者周期性地執(zhí)行某個任務(wù)。可以指定線程數(shù)量。
newSingleThreadScheduledExecutor實現(xiàn)了和上面一樣的功能,不過線程池的大小為1。
ScheduledExecutorService有三個方法可以有計劃地執(zhí)行任務(wù)。如:
-
schedule(Runnable command, long delay, TimeUnit unit);
該方法可以在給定的延時后,執(zhí)行一個任務(wù); -
scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
該方法以任務(wù)開始執(zhí)行的時間為initialDelay,加上周期period,就是下一個任務(wù)開始執(zhí)行的時間,以此類推,因此這個方法任務(wù)調(diào)度的頻率是一定的; -
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
該方法表示每執(zhí)行完一個任務(wù),延遲delay
的時間后,開始執(zhí)行下一個任務(wù),initialDelay
還是表示任務(wù)開始的初始時延,上一個任務(wù)結(jié)束的時間點與下一個任務(wù)開始的時間點之差是固定的,固定為delay。
即使單個任務(wù)的執(zhí)行時間超過調(diào)度周期,scheduleAtFixedRate也不會讓多個任務(wù)堆疊,比如任務(wù)執(zhí)行需要8s,而調(diào)度周期是2s,調(diào)度第二個任務(wù)時,第一個還沒執(zhí)行完,因此為了避免任務(wù)堆疊,此時調(diào)度周期會變成8s;而采用scheduleWithFixedDelay,兩個任務(wù)之間的實際間隔會變成10s,8s的執(zhí)行+2s的delay。
Executors是線程池的工廠類,通過調(diào)用它的靜態(tài)方法如
Executors.newCachedThreadPool();
Executors.newFixedThreadPool(n);
可返回一個線程池。這些靜態(tài)方法統(tǒng)一返回一個ThreadPoolExecutor
,只是參數(shù)不同而已。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
包括以上幾個參數(shù),其中:
- corePoolSize:指定了線程池中線程的數(shù)量;
- maximumPoolSize:線程池中的最大線程數(shù)量;
- keepAliveTime:當線程池中線程數(shù)量超過corePoolSize時,多余的空閑線程的存活時間;
- unit:上一個參數(shù)keepAliveTime的單位
- 任務(wù)隊列,被提交但還未被執(zhí)行額任務(wù)
- threadFactory:線程工廠,用于創(chuàng)建線程,一般用默認工廠即可。
- handler:拒絕策略。當任務(wù)太多來不及處理的時候,采用什么方法拒絕任務(wù)。
最重要的是任務(wù)隊列和拒絕策略。
任務(wù)隊列主要有ArrayBlockingQueue有界隊列、LinkedBlockingQueue無界隊列、SynchronousQueue直接提交隊列。
使用ArrayBlockingQueue,當線程池中實際線程數(shù)小于核心線程數(shù)時,直接創(chuàng)建線程執(zhí)行任務(wù);當大于核心線程數(shù)而小于最大線程數(shù)時,提交到任務(wù)隊列中;因為這個隊列是有界的,當隊列滿時,在不大于最大線程的前提下,創(chuàng)建線程執(zhí)行任務(wù);若大于最大線程數(shù),執(zhí)行拒絕策略。
使用LinkedBlockingQueue時,當線程池中實際線程數(shù)小于核心線程數(shù)時,直接創(chuàng)建線程執(zhí)行任務(wù);當大于核心線程數(shù)而小于最大線程數(shù)時,提交到任務(wù)隊列中;因為這個隊列是有無界的,所以之后提交的任務(wù)都會進入任務(wù)隊列中。newFixedThreadPool就采用了無界隊列,同時指定核心線程和最大線程數(shù)一樣。
使用SynchronousQueue時,該隊列沒有容量,對提交任務(wù)的不做保存,直接增加新線程來執(zhí)行任務(wù)。newCachedThreadPool使用的是直接提交隊列,核心線程數(shù)是0,最大線程數(shù)是整型的最大值,keepAliveTime是60s,因此當新任務(wù)提交時,若沒有空閑線程都是新增線程來執(zhí)行任務(wù),不過由于核心線程數(shù)是0,當60s就會回收空閑線程。
當實際線程數(shù)超過maxPoolSize時,該采取什么樣的策略?
- AbortPolicy:丟棄任務(wù)并拋出異常;
- CallerRunPolicy:該任務(wù)被線程池拒絕,由調(diào)用execute方法的線程執(zhí)行該任務(wù);
- DiscardOldestPolicy:丟棄最老的一個,也就是馬上要執(zhí)行的一個任務(wù);
- DiscardPolicy:默默丟棄被拒絕的任務(wù),體現(xiàn)在代碼中就是什么也不做。
下面看看CallerRunPolicy怎么拒絕的
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
DiscardOldestPolicy是這樣做的
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 最老的一個請求在隊列頭部
e.execute(r);
}
}
實現(xiàn)一個簡單的線程池
實現(xiàn)一個類似于Executors.newFixedThreadPool(n)
的固定大小線程池,當小于corePoolSize時候,優(yōu)先創(chuàng)建線程去執(zhí)行該任務(wù);當超過該值時,將任務(wù)提交到任務(wù)隊列中,然后各個線程從任務(wù)隊列中取任務(wù)來執(zhí)行。
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class MyThreadPool {
private int workerCount;
private int corePoolSize;
private BlockingQueue<Runnable> workQueue;
private Set<Worker> workers;
private volatile boolean RUNNING = true;
public MyThreadPool(int corePoolSize) {
this.corePoolSize = corePoolSize;
workQueue = new LinkedBlockingQueue<>();
workers = new HashSet<>();
}
public void execute(Runnable r) {
if (workerCount < corePoolSize) {
addWorker(r);
} else {
try {
workQueue.put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void addWorker(Runnable r) {
workerCount++;
Worker worker = new Worker(r);
Thread t = worker.thread;
workers.add(worker);
t.start();
}
class Worker implements Runnable {
Runnable task;
Thread thread;
public Worker(Runnable task) {
this.task = task;
this.thread = new Thread(this);
}
@Override
public void run() {
while (RUNNING) {
Runnable task = this.task;
// 執(zhí)行當前的任務(wù),所以把這個任務(wù)置空,以免造成死循環(huán)
this.task = null;
if (task != null || (task = getTask()) != null) {
task.run();
}
}
}
}
private Runnable getTask() {
Runnable r = null;
try {
r = workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return r;
}
public static void main(String[] args) {
MyThreadPool threadPool = new MyThreadPool(5);
Runnable r = new Writer();
for (int i = 0; i < 10; i++) {
threadPool.execute(r);
}
}
}
class Writer implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " ");
}
}
Worker實現(xiàn)了Runnale,是真正執(zhí)行任務(wù)的類。當線程池中工作線程小于核心線程時候,調(diào)用addWorker直接start線程執(zhí)行它的第一個任務(wù)。否則,將任務(wù)放入任務(wù)隊列中,等線程來執(zhí)行它們。Worker中的run方法是一個死循環(huán),執(zhí)行第一個任務(wù)(addWorker時調(diào)用start方法執(zhí)行的那個任務(wù)),或者通過getTask方法不斷從任務(wù)隊列中取得任務(wù)來執(zhí)行。正是getTask方法實現(xiàn)了線程的復用,即一個線程雖然只能調(diào)用一次start方法,但是后續(xù)的任務(wù)可以在Worker的run方法里直接調(diào)用任務(wù)的run方法得以執(zhí)行。簡單來說就是在Worker的run里調(diào)用任務(wù)的run方法。
任務(wù)全部執(zhí)行完畢后,線程池需要被關(guān)閉,否則程序一直死循環(huán)。上述代碼中并沒有實現(xiàn)shutdown()
方法。