一、概念、生產者消費者模式
- 隊列
先進先出的一個數據結構 - 阻塞隊列
(1)當隊列爲空的時候,從裏面取數據的動作會被阻塞。
(2)當隊列滿的時候,往裏面放元素的動作會被阻塞。 - 生產者消費者模式
在生產者和消費者模式之間插入一個阻塞隊列。生產者生產的東西放到隊列容器中,而消費者直接從隊列中取出東西進行消費。這樣可以使得生產者和消費者之間的解耦,也可以使得生產者和消費者的性能的均衡,避免生產者生產東西過快,一直等待消費者消費,或者消費者消費過快等待生產者生產東西。
二、JDK中實現的阻塞隊列
在JDK中BlockingQueue類封裝了阻塞隊列的接口
其中包含了幾個核心的方法:
- add和remove方法,非阻塞方法
當隊列滿的時候往裏面add數據,會拋出異常,當隊列爲空的時候從裏面remove數據,會拋出異常。
boolean add(E e);
boolean remove(Object o);
- offer和poll方法,非阻塞方法
offer:往隊列中裏插入一個元素,當隊列滿的時候,返回一個false,
poll:從隊列取出一個元素,當隊列爲空的時候,返回一個null
boolean offer(E e);
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
- take和put方法,真正意義上的阻塞方法
take:當隊列爲空的時候,從隊列中取元素,會被阻塞
put:當隊列滿的時候,往隊列中插入元素,會被阻塞
E take() throws InterruptedException;
void put(E e) throws InterruptedException;
三、JDK中常用的阻塞隊列
ArrayBlockingQueue
是一個用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認情況下不保證線程公平的訪問隊列,所謂公平訪問隊列是指阻塞的線程,可以按照阻塞的先後順序訪問隊列,即先阻塞線程先訪問隊列。非公平性是對先等待的線程是非公平的,當隊列可用時,阻塞的線程都可以爭奪訪問隊列的資格,有可能先阻塞的線程最後才訪問隊列。初始化時有參數可以設置LinkedBlockingQueue
是一個用鏈表實現的有界阻塞隊列。此隊列的默認和最大長度爲Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。PriorityBlockingQueue
PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認情況下元素採取自然順序升序排列。也可以自定義類實現compareTo()方法來指定元素排序規則,或者初始化PriorityBlockingQueue時,指定構造參數Comparator來對元素進行排序。需要注意的是不能保證同優先級元素的順序。DelayQueue
是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。SynchronousQueue:一個不存儲元素的阻塞隊列。
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
以上的阻塞隊列都實現了BlockingQueue接口,也都是線程安全的。
在使用阻塞隊列的時候,儘量使用有界的阻塞隊列,有界的阻塞隊列,規定了最大容量,當隊列滿的時候,往裏面插入元素,會被阻塞。而無界的阻塞隊列,可以一直往裏插入元素,會佔用內存,最終總會使得內存溢出。
緩存系統的設計
可以用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢DelayQueue。一旦可以從隊列中查到元素,表示緩存有效期到了。
四、線程池
Java中的線程池是運用場景最多的併發框架。合理的使用線程池能夠帶來以下3個好處:
(1)降低資源的消耗:通過重複利用線程池中的線程,降低線程創建和銷燬帶來的開銷。
(2)提高響應速度:當提交一個任務的時候,不需要等待線程的創建就能立即執行。
(3)提高線程的可管理性:在操作系統中,線程是非常稀缺的資源,如果無限制的創建線程,不僅會消耗系統資源,還會降低系統的穩定性。
1、JDK中線程池的實現
在JDK中使用ThreadPoolExecutor 作爲線程池的核心類,用來執行被提交的任務。
ThreadPoolExecutor 的使用舉例
package com.it.test.thread.consumer_product.retranlock;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class LockTest {
public static void main(String[] args) {
LinkedBlockingQueue queue = new LinkedBlockingQueue();
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 30, TimeUnit.MINUTES, queue, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
}, new ThreadPoolExecutor.DiscardOldestPolicy());
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":你好");
}
});
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":世界");
}
});
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":hello");
}
});
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":world");
}
});
System.out.println("阻塞隊列的長度:"+queue.size());
}
}
pool-1-thread-1:你好
pool-1-thread-2:世界
pool-1-thread-2:world
pool-1-thread-1:hello
阻塞隊列的長度:2
ThreadPoolExecutor 類的解析
(1)構造方法
構造方法中包含了7個核心參數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心線程數,當提交一個任務的時候,默認情況下線程池會新建一個線程執行任務,直到線程數等於核心線程數。
如果當前線程數爲corePoolSize(核心線程數),繼續提交任務的時候,會被保存到阻塞隊列中等待被執行。
如果執行了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有的核心線程。maximumPoolSize
線程池允許的最大線程數,如果當阻塞隊列中的任務滿的時候,繼續提交任務,線程池會繼續創建新的線程執行任務。前提是當前線程數要小於等於最大線程數。keepAliveTime
線程空閒的時候,存活的時間。即當沒有任務執行的時候,線程繼續存活的時間。默認情況下該參數只有在線程數大於核心線程數纔有用TimeUnit
時間單位workQueue
存放任務的BlockingQueue阻塞隊列,如果當前線程數量等於核心線程數,繼續提交任務的時候,會將任務存放到阻塞隊列,等待被執行。threadFactory
線程池中創建線程的工廠,一般只是對線程做一些策略,例如自定義線程名字等等handler
拒絕策略,當線程池中的線程超出最大線程數maximumPoolSize的時候,繼續提交任務,會觸發拒絕策略。在JDK中有四種拒絕策略的核心類:
(1)AbortPolicy:直接拋出異常,默認策略;
(2)CallerRunsPolicy:用調用者所在的線程來執行任務;
(3)DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
(4)DiscardPolicy:直接丟棄任務;
線程池的工作機制
(1)如果運行的線程池少於核心線程數,提交任務的時候將創建新的線程來執行任務。
(2)如果運行的線程數等於核心線程數,提交任務的時候將會把任務存放到阻塞隊列中(BlockingQueue)。
(3)如果阻塞隊列滿的時候,無法再添加,則創建新的線程來執行任務。
(4)如果創建的線程超過最大線程數,將會被拒絕。調用RejectedExecutionHandler.rejectedExecution()方法
提交任務
execute提交任務,不需要任務返回值。所以無法判斷任務是否被線程池執行成功
submit提交任務,有返回值,返回一個future類型的對象。可以通過future對象判斷任務是否執行成功,通過future的get方法,獲取任務的返回值。在調用get方法的時候,會阻塞當前線程,直到任務完成爲止。也可以在get方法設置返回時間。
關閉線程池
可以通過shutdown和shutdownNow來關閉線程池。
合理的配置線程池
合理配置線程池之前首先要分析任務的特性:
(1)CPU密集型
核心線程數 = Ncpu+1,
(2)IO密集型
核心線程數 = 2*Ncpu
(3)混合型
可以根據不同的任務類型對線程池進行拆分多個。
ThreadPoolExecutor源碼分析
- 構造方法
傳入核心線程數,阻塞隊列等參數,設置到當前成員
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 提交任務
根據設置的核心線程數和阻塞隊列判斷是否要調用addWorker添加任務還是reject拒絕任務
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
- 添加任務
private boolean addWorker(Runnable firstTask, boolean core) {
.......
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
(1)w = new Worker(firstTask);
創建一個Worker對象,在Worker構造方法中創建一個線程,傳入Worker當前對象,Worker實現了Runnable接口
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
(2)添加Worker到HashSet中(HashSet<Worker> workers),添加成功則啓動woker中的線程
workers.add(w);
....
if (workerAdded) {
t.start();
workerStarted = true;
}
因爲Worker實現了Runnable接口,並且在創建線程的時候傳入了當前Worker對象,所以啓動線程的時候調用了Worker中的run方法
public void run() {
runWorker(this);
}
接着調用runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
runWorker方法中,開啓了一個while 循環,獲取第一次傳進的任務或者是
getTask(),阻塞隊列中的任務。然後調用 task.run();這樣就回調到了我們提交任務的時候實現的run方法。
源碼小結
在ThreadPoolExecutor中用一個HashSet用來存放woker,而woker的構造方法中創建了線程,因此可以理解爲woker存放了核心線程。當HashSet的長度等於核心線程數的時候,則將任務提交到阻塞隊列。
在執行任務的時候獲取當前提交的task或者從阻塞隊列中取出task執行。