1.線程
線程是調度cpu的最小單元,也叫輕量級的進程。
2.兩種線程模型
- 用戶級線程(ULT):指不需要內核支持而在用戶程序中實現的線程,它的內核的切換是由用戶態程序自己控制內核的切換,不需要內核的干涉。但是它不能像內核級線程一樣更好的運用多核CPU。
- 內核級線程(KLT):切換由內核控制,當線程進行切換的時候,由用戶態轉化爲內核態。切換完畢要從內核態返回用戶態。可以很好的運用多核CPU,就像Windows電腦的四核八線程,雙核四線程一樣。
3.線程池
- 線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷燬線程池,添加新任務;
- 工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
- 任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;
- 任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。
4.常用線程池
類型 | 說明 |
---|---|
SingleThreadExecutor | 單一線程的線程池 |
FixedThreadPool | 固定大小的線程池 |
CachedThreadPool | 可緩衝的線程池 |
ScheduledThreadPool | 無限制大小線程池,定時場景 |
4.1 SingleThreadExecutor
單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序執行。
public class singleThreadExecutor {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print(1 + " ");
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print(2 + " ");
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print(3 + " ");
}
}
});
executor.shutdown();
}
}
4.2 CachedThreadPool
可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
executor.shutdown();//關閉線程
}
}
4.3 FixedThreadPool
創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待.
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()
public class FixedThreadPool {
public static void main(String[] args) {
//因爲線程池大小爲3,每個任務輸出index後sleep 2秒,所以每兩秒打印3個數字。
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
executor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executor.shutdown();
}
}
4.4 ScheduledThreadPool
創建一個定長線程池,支持定時及週期性任務執行。
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);//等待三秒執行
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);//表示延遲1秒後每3秒執行一次。
}
}
5. ThreadPoolExecutor+BlockingQueue使用詳解
5.1 使用場景
- 需要的子線程數量很多,但是數量不確定。
- 子線程有自己的優先級,根據優先級來確定執行的先後順序。
- 監聽線程池的開始,結束,關閉等狀態。
5.2 ThreadPoolExecutor構造方法
//使用給定的初始參數和默認線程工廠以及拒絕的執行處理程序創建新的ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
//使用給定的初始參數和默認拒絕執行處理程序創建新的ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
//使用給定的初始參數和默認線程工廠創建一個新的code ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
//使用給定的初始參數創建一個新的ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
主要參數
- corePoolSize:核心線程數
核心線程會一直存活,及時沒有任務需要執行。當線程數小於核心線程數時,即使有線程空閒,線程池也會優先創建新線程處理。 - maxPoolSize:最大線程數
當線程數>=corePoolSize,且任務隊列已滿時。線程池會創建新線程來處理任務。
當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而拋出異常。 - keepAliveTime:非核心線程閒置時的超時時長超過這個時長,非核心線程就會被回收。當
ThreadPoolExecutor
的allowCoreThreadTimeOut
屬性設置爲true
時,keepAliveTime
同樣會作用於核心線程。 - unit:用於指定 keepAliveTime 參數的時間單位
常用的有TimeUnit .MILLISECONDS
和TimeUnit .SECONDS
。 - workQueue:線程池中的任務隊列通過線程池的
execute
方法提交的Runnable
對象會存儲在這個參數中。 - threadFactory:線程工廠爲線程池提供創建新的線程的功能。
threadFactory
是一個接口,它只有一個方法:public abstract Thread newThread (Runnable r)
; - RejectedExecutionHandler:通常叫做拒絕策略在線程池已經關閉的情況下或者當線程數已經達到
maxPoolSize
且隊列已滿。只要滿足其中一種時,在使用execute()
來提交新的任務時將會拒絕,而默認的拒絕策略是拋一個RejectedExecutionException
異常。
5.3 ThreadPoolExecutor執行順序
- 當線程數小於核心線程數時,創建線程。
- 當線程數大於等於核心線程數,且任務隊列未滿時,將任務放入任務隊列。
- 當線程數大於等於核心線程數,且任務隊列已滿。
若線程數小於最大線程數,創建線程。
若線程數等於最大線程數,拋出異常,拒絕任務 。
5.4 BlockingQueue
BlockingQueue
是一個特殊的隊列,當我們從BlockingQueue
中取數據時,如果BlockingQueue
是空的,
則取數據的操作會進入到阻塞狀態,當 BlockingQueue
中有了新數據時,這個取數據的操作又會被重新喚醒。
同理,如果 BlockingQueue
中的數據已經滿了,往BlockingQueue
中存數據的操作又會進入阻塞狀態,直到 BlockingQueue
中又有新的空間,存數據的操作又會被重新喚醒。它的泛型限定它是用來存放 Runnable
對象的。
5.5 幾種常用的BlockingQueue
- ArrayBlockingQueue:
這個表示一個規定了大小的BlockingQueue
,ArrayBlockingQueue
的構造函數接受一個int
類型的數據,該數據表示BlockingQueue
的大小,存儲在ArrayBlockingQueue
中的元素按照FIFO
(先進先出)的方式來進行存取。 - LinkedBlockingQueue:
這個表示一個大小不確定的BlockingQueue
,在LinkedBlockingQueue
的構造方法中可以傳一個 int 類型的數據,這樣創建出來的LinkedBlockingQueue
是有大小的,默認LinkedBlockingQueue
的大小就爲Integer.MAX_VALUE
。 - PriorityBlockingQueue:
這個隊列和LinkedBlockingQueue
類似,不同的是PriorityBlockingQueue
中的元素不是按照 FIFO
來排序的,而是按照元素的Comparator
來決定存取順序的(這個功能也反映了存入PriorityBlockingQueue
中的數據必須實現了 Comparator 接口)。 - SynchronousQueue:
這個是同步 Queue
,屬於線程安全的 BlockingQueue
的一種,在SynchronousQueue
中,生產者線程的插入操作必須要等待消費者線程的移除操作,Synchronous
內部沒有數據緩存空間,因此我們無法對SynchronousQueue
進行讀取或者遍歷其中的數據,元素只有在你試圖取走的時候纔有可能存在。我們可以理解爲生產者和消費者互相等待,等到對方之後然後再一起離開。
5.6 常用線程池對應的BlockingQueue
- newFixedThreadPool()—>
LinkedBlockingQueue
- newSingleThreadExecutor()—>
LinkedBlockingQueue
- newCachedThreadPool()—>
SynchronousQueue
- newScheduledThreadPool()—>
DelayedWorkQueue
使用代碼示例
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
BlockingQueue bq = new ArrayBlockingQueue(5);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 30, 10, TimeUnit.SECONDS, bq);
executor.allowCoreThreadTimeOut(true);
for (int i = 0; i < 30; i++) {
executor.execute(new PrintDate());
}
}
public static class PrintDate implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ":" + System.currentTimeMillis());
}
}
}
如何設置參數
默認值
corePoolSize=1
queueCapacity=Integer.MAX_VALUE
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
rejectedExecutionHandler=AbortPolicy()
如何來設置
tasks :每秒的任務數,假設爲500~1000
taskcost:每個任務花費時間,假設爲0.1s
responsetime:系統允許容忍的最大響應時間,假設爲1s
- corePoolSize = 每秒需要多少個線程處理?
- threadcount = tasks/(1/taskcost) =tasks·taskcout = (500~1000)·0.1 = 50~100 個線程。corePoolSize設置應該大於50
根據8020原則,如果80%的每秒任務數小於800,那麼corePoolSize設置爲80即可 - queueCapacity = (coreSizePool/taskcost)responsetime
計算可得 queueCapacity = 80/0.11 = 80。意思是隊列裏的線程可以等待1s,超過了的需要新開線程來執行
切記不能設置爲Integer.MAX_VALUE,這樣隊列會很大,線程數只會保持在corePoolSize大小,當任務陡增時,不能新開線程來執行,響應時間會隨之陡增。 - maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
計算可得 maxPoolSize = (1000-80)/10 = 92
最大任務數-隊列容量)/每個線程每秒處理能力 = 最大線程數 - rejectedExecutionHandler:根據具體情況來決定,任務不重要可丟棄,任務重要則要利用一些緩衝機制來處理
- keepAliveTime和allowCoreThreadTimeout採用默認通常能滿足。
到這裏線程池的基本使用就差不多了。