線程和線程池
1. 線程
1.1 什麼是線程&多線程
線程:
線程是進程的一個實體,是CPU
調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與屬同一個進程的其他線程共享進程所擁有的全部資源。
多線程:
多線程指在單個程序中可以同時運行多個不同的線程執行不同的任務。
多線程編程的目的,就是“最大限度地利用CPU
資源”,當某一線程地處理不需要佔用CPU
而只和IO
等資源打交道時,讓需要佔用CPU
的其他線程有其他機會獲得CPU
資源。
1.2 線程實現的方式
- 實現 Runnable 接口
- 繼承 Thread 類
- 實現 Callable 接口
1.3 線程的生命週期&狀態
1.4 線程執行順序
/**
* @author wangzhao
* @date 2019/9/15 19:48
*/
public class ThreadSort {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
System.out.println("thread1");
});
Thread thread2 = new Thread(()->{
System.out.println("thread2");
});
Thread thread3 = new Thread(()->{
System.out.println("thread3");
});
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
thread3.join();
}
}
join
線程的運行順序:
thread1
thread2
thread3
thread1.join()
的含義是運行thread1.join()
所在的線程等待等待 thread1
線程終止後才從thread1.join()返回。
即當前線程(main
)只有等待調用該方法的線程結束,才能繼續向下執行,否則一直被阻塞在join()
處。
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
我們需要知道的是,線程調用wait()方法必須獲取鎖,所以join()
方法是被synchronized
修飾的,synchronizaed
修飾在方法層面相當於synchronized(this)
,this
就是thread1
本身的實例。
爲什麼join
阻塞的是主線程,而不是thread1
對象?
因爲主線程會持有thread1
這個對象的鎖,然後thread1
對象調用wait()
方法去阻塞,而這個方法的調用者是在主線程中的。所以造成主線程阻塞。
爲什麼thread1
線程執行完畢就能夠喚醒主線程呢?或者說是在什麼時候喚醒的?
通過wait
方法阻塞的線程,需要通過notify
或者notifyall
來喚醒。所以在線程執行完畢以後會有一個喚醒的操作,只是我們不需要關心。
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
assert(this == JavaThread::current(), "thread consistency check");
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
assert(!this->has_pending_exception(), "ensure_join should have cleared");
...
觀察一下 ensure_join(this)
這行代碼上的註釋,喚醒處於等待的線程對象,這個是在線程終止之後做的清理工作,這個方法的定義代碼片段如下:
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
//這裏是清除native線程,這個操作會導致isAlive()方法返回false
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);//注意這裏
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
ensure_join
方法中,調用 lock.notify_all(thread)
; 喚醒所有等待thread
鎖的線程,意味着調用了join
方法被阻塞的主線程會被喚醒;
Thread.join
其實底層是通過wait/notifyall
來實現線程的通信達到線程阻塞的目的;當線程執行結束以後,會觸發兩個事情,第一個是設置native
線程對象爲null
、第二個是通過notifyall
方法,讓等待在thread1
對象鎖上的線程被喚醒。
1.5 Callable & Future
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
return "hello world";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<String>(new CallableTest());
new Thread(task).start();
System.out.println(task.get());
}
}
hello world
爲什麼調用get()
方法會被阻塞?
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
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 {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return
report(s);
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
通過state
來判斷當前任務是否執行完,如果沒有執行完則阻塞。
1.6 總結
實現Runnable接口相比繼承Thread類有如下優勢:
1)可以避免由於Java的單繼承特性而帶來的侷限
2)增強程序的健壯性,代碼能夠被多個線程共享,代碼與數據是獨立的
3)線程池只能放入實現Runnable
或Callable
類線程,不能直接放入繼承Thread
的類
實現Runnable接口和實現Callable接口的區別:
1)Runnable
是自從Java1.1
就有了,而Callable
是1.5
之後才加上去的
2)實現Callable
接口的任務線程能返回執行結果,而實現Runnable
接口的任務線程不能返回結果
3)Callable
接口的call()
方法允許拋出異常,而Runnable
接口的run()
方法的異常只能在內部消化,不能向上拋
4)加上線程池允許,Runnable
使用ExecutorService
的execute()
方法,Callable
使用submit()
方法
注意:Callable
接口支持返回指向結果,此時需要調用FutureTask.get()
方法實現,此方法會阻塞主線程直到獲取返回結果,當不調用此方法時,主線程不會阻塞。
2. 線程池
2.1 線程池是什麼
線程池是擁有若干已經創建好的線程的緩衝池,調用者可以直接通過線程池獲取已經創建好的線程執行任務。
2.2 線程池的好處
- 降低資源消耗:重複利用已創建的線程執行新的任務
- 提高響應速度:當任務到達時,任務可以不需要等到線程創建就能立即執行
- 提高線程的可管理性:由線程池統一分配、調優和監控線程
2.3 線程池實現原理
當提交一個新任務到線程池時,線程池的處理流程如下:
ThreadPoolExecutor
執行 execute()
方法的示意圖:
ThreadPoolExecutor
執行execute
方法分下面4種情況:
1)如果當前運行的線程少於 corePoolSize
,則創建新線程來執行任務(注意,執行這一步驟需要獲取全局鎖)
2)如果運行的線程等於或多於corePoolSize
,則將任務加入BlockingQueue
3)如果無法將任務加入BlockingQueue
(隊列已滿),則創建新的線程來處理任務(注意,執行這一步驟需要獲取全局鎖)
4)如果新創建線程將使當前運行的線程超出 maximumPoolsize
,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()
方法。
爲什麼要將線程池分爲maximumPool 和 corePool,一個pool不行嗎?
是爲了在執行execute()
方法 ,儘可能地避免獲取全局鎖。在ThreadPoolExecuto
r完成預熱之後(當前運行地線程數大於等於corePoolSize
),幾乎所有的 execute()
方法調用都是執行步驟2 ,而 步驟2不需要獲得鎖。
同時,也可以減少線程被創建的數量。
2.3.1 源碼分析
成員變量 ctl
是一個 Integer
的原子變量,用來記錄線程池狀態和線程池中線程個數。
假設 Integer
類型是 32
位二進制表示,則其中高 3
位用來表示線程池狀態,後面 29
位用來記錄線程池線程個數。
// 默認是 RUNNING 狀態,線程個數爲0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 線程個數掩碼位數,並不是所有平臺的 int 類型都是32位,即具體平臺下Integer的二進制位數-3後的剩餘位數所
// 表示的數纔是線程個數
private static final int COUNT_BITS = Integer.SIZE - 3;
// 線程最大個數(低29位)00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 線程池狀態:
// (高三位) 11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// (高三位) 00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// (高三位) 00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// (高三位) 01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// (高三位) 01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 獲取高3位(運行狀態)
private static int runStateOf(int c){
return c & ~CAPACITY;
}
// 獲取低29位(線程個數)
private static int workerCountOf(int c){
return c & CAPACITY;
}
// 計算 ctl 新值(線程狀態與線程個數)
private static int ctlOf(int rs,int wc){
return rs | wc;
}
線程池狀態含義如下:
RUNNING
:接受新任務並且處理阻塞隊列裏的任務SHUTDOWN
:拒絕新任務但是處理阻塞隊列裏的隊伍STOP
:拒絕新任務並且拋棄阻塞隊列裏的任務,同時會中斷正在處理的任務TIDYING
:所有任務都執行完(包含阻塞隊列裏面的任務)後當前線程池活動線程數爲0,將要調用
terminated
方法。TERMINSTED
:終止狀態,terminated
方法調用完成以後的狀態。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1、如果線程數小於核心線程數,則創建線程並執行當前任務
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2、如果線程數大於等於基本線程數或線程創建失敗,將當前任務放到工作隊列中
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);
}
// 3、嘗試開啓新線程,擴容至maxPoolSize
else if (!addWorker(command, false))
// 4、如果失敗,則執行響應的策略
reject(command);
}
2.3.2 工作線程
線程池創建線程時,會將線程封裝成工作線程Worker
,Worker
在執行完任務後,還會循環獲取工作隊列的任務來執行。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
}
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);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
ThreadPoolExecutor
中線程執行任務的示意圖:
線程池中的線程執行任務分兩種情況:
1)在 execute()
方法中創建一個線程時,會讓這個線程執行當前任務
2)這個線程執行完上圖中1
的任務後,會反覆從 BlockingQueue
獲取任務來執行。
2.4 線程池的使用
2.4.1 線程池的創建
new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1)corePoolSize
(線程池的基本大小):當提交一個任務到線程池,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,需要等到需要執行的任務數大於線程池基本大小時就不再創建。
prestartCoreThread()
, 線程池提前創建並啓動所有的基本線程。
2)maximumPoolSize
(線程池最大數量):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程池小於最大線程數,則線程會再創建新的線程執行任務。如果使用了無界的隊列這個參數沒有效果。
3)workQueue
(任務隊列):用於保證等待執行的任務的阻塞隊列。
有如下幾個可以選擇的阻塞隊列:
ArrayBlockingQueue
:基於數組結構的有界阻塞隊列。此隊列按FIFO原則對元素進行排序。
LinkedBlockingQueue
:基於鏈表結構的有界阻塞隊列。此隊列按FIFO排序元素,吞吐量通常高於ArrayBlockingQueue。 靜態工廠方法 Executors.newFixedThreadPool()
使用了這個隊列
SynchronousQueue
:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態。吞吐量通常高於LinkedBlockingQueue
。靜態工廠方法 Executors.newCachedThreadPool()
使用了這個隊列
PriorityBlockingQueu
e:一個具有優先級的無限阻塞隊列。
4)ThreadFactory
:用於設置創建線程的工廠,可以通過線程工廠可以對創建的線程做相關設置。
5)RejectedExecutionHandler
(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy
.
1. AbortPolicy
:直接拋出異常
2. CallerRunsPolicy
:使用調用者所在的線程來運行任務
3. DiscardOldestPolicy
:丟棄隊列裏最老的一個任務,並執行當前任務
4. DiscardPolicy
:不處理,丟棄掉
6)keepAliveTime
(線程活動保持時間):線程池的工作線程空閒後,保持存活的時間。如果任務很多,並且每個任務執行的時間比較短,可以調大時間,提高線程的利用率。
7)TimeUnit
(線程活動保持時間的單位)
2.4.2 向線程池提交任務
可以使用兩個方法向線程池提交任務,分別爲 execute()
和submit()
方法:
2.4.2.1 excute()
該方法用於提交不需要返回值的任務。
public void execute(Runnable command) {
// (1)如果任務爲null,拋出NPE異常
if (command == null)
throw new NullPointerException();
// (2)獲取當前線程池的狀態+線程個數變量的組合值
int c = ctl.get();
// (3)當前線程池中線程個數是否小於 corePoolSize ,小於則開啓新線程運行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// (4)如果線程池處於 RUNNING 狀態,則添加任務到阻塞隊列
if (isRunning(c) && workQueue.offer(command)) {
// (4.1)二次檢查
int recheck = ctl.get();
// (4.2) 如果當前線程池狀態不是 RUNNING 則從隊列中刪除任務,並執行拒絕策略
if (! isRunning(recheck) && remove(command))
reject(command);
// (4.3)否則如果當前線程爲空,則添加一個線程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// (5)如果隊列滿,則新增線程,新增失敗則執行拒絕策略
else if (!addWorker(command, false))
reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 檢查隊列是否只在必要時爲空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 循環 CAS 增加線程個數
for (;;) {
int wc = workerCountOf(c);
// 如果線程個數超限則返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS 增加線程個數,同時只有一個線程成功
if (compareAndIncrementWorkerCount(c))
break retry;
// CAS 失敗了,則看線程池狀態是否變化了,變化則跳到外層循環重新嘗試獲取線程池
// 狀態,否則內存循環重新 CAS
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 到這裏說明 CAS 成功
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 創建一個worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 加獨佔鎖,爲了實現 worker 同步,因爲可能多個線程調用了線程池的 execute 方法
mainLock.lock();
try {
// 重新檢查線程池狀態,以避免在獲取鎖前調用了 shutdown 接口
int rs = runStateOf(ctl.get());
if (rs < 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;
}
(1)雙重循環的目的是通過CAS
操作增加線程數
(2)把併發安全的任務添加到 worker
裏面,並且啓動任務執行
Worker(Runnable firstTask) {
setState(-1); // 在調用 runworker 之前禁止中斷
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 將 state 設置爲0,允許中斷
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);
Throwable thrown = null;
try {
// 執行任務
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 執行任務完畢後幹一些事情
afterExecute(task, thrown);
}
} finally {
task = null;
// 統計當前 Worker 完成了多少個任務
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 執行清理工作
processWorkerExit(w, completedAbruptly);
}
}
2.4.2.2 submit()
用於提交需要返回值的任務。
線程池會返回一個future
類型的對象,通過這個future
對象可以判斷任務是否執行成功,並且可以通過future
的get()
方法來獲取返回值,get()
方法會阻塞當前線程直到任務完成。
Future<?> future = threadPool.submit(task);
Object s = future.get();
可以調用 shutdown()
或 shurdownNow()
方法來關閉線程池。
原理:遍歷線程池中的工作線程,然後逐個調用線程的interrupt
方法來中斷線程,所以無法響應線程中斷的任務可能永遠無法終止。
shotdownNow()
將線程池狀態設置成STOP
,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表。
而shutdown
只是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。