1. JAVA 并發(fā)編程基礎
從啟動一個線程到線程間不同的通信方式.
1.1 線程
線程是系統(tǒng)調(diào)度的最小單位, 擁有各自的計數(shù)器, 堆棧和局部變量等屬性.
1.1.1.1 為什么需要多線程
- 更多的CPU 核心.
- 一個線程同一個時刻只能運行在一個CPU 核心上.
- 更快的響應時間.
- 更好的編程模型.
1.1.2 線程優(yōu)先級
- OS 采用時分的形式調(diào)度線程的運行.
- OS 會分出一個個的時間片, 線程會被分配到若干個時間片.
- 當線程的時間片用完了就發(fā)生線程調(diào)度, 并等待下次調(diào)度.
- 線程的優(yōu)先級決定了線程需要被分配的CPU 資源的多少.
- 頻繁阻塞(休眠或I/O)的線程設置較高的優(yōu)先級.
- 偏重計算(需要較多CPU 時間的) 設置較低優(yōu)先級, 以防止CPU 被獨占.
- 但是, OS 和JVM 對線程優(yōu)先級不做任何保證, 程序的正確性不能依賴于線程的優(yōu)先級高低.
1.1.3 線程的狀態(tài)
1.1.4 Daemon 線程
- 支持型線程: 主要用于程序中后臺調(diào)度及支持性工作.
- 當一個JVM 中不存在非Daemon 線程的時候, JVM 將會退出.
- 當JVM 退出時, Daemon 線程中的finally 塊并不一定會被執(zhí)行.
- Daemon 線程不能依賴于finally 塊來執(zhí)行關閉或清理資源的邏輯.
1.2 啟動和終止線程
1.2.1 構(gòu)造和啟動線程
- 新構(gòu)造的線程由其parent 線程來進行空間分配, 同時child 繼承了parent 的優(yōu)先級, 是否為Daemon, contextClassLoader 等屬性.
- 啟動線程時, 最好設置線程名稱, 這樣在使用jstack 分析問題時, 能獲得更多有用的信息.
1.2.2 中斷.
- 中斷是線程的一個標示位屬性, 它表示一個運行中的線程, 是否被其他線程進行了中斷操作.
- 線程通過isInterrupted() 來檢查自身是否被中斷, 同時調(diào)用Thread.interrupted() 進行中斷復位.
- 方法在拋出InterruptedException 之前, JVM 會將該線程的中斷標示位清除, 然后再拋出異常.
1.2.3 過期的suspend(), resume(), stop().
- 過期的主要原因:
- 調(diào)用suspend() 后, 線程不會釋放已經(jīng)占有的資源(如鎖), 而是占用著資源進入睡眠狀態(tài), 易引發(fā)死鎖.
- 調(diào)用stop() 在終結(jié)一個線程時不會爆炸線程的資源正常釋放, 通常是沒有給與線程完成資源釋放的機會, 從而導致程序運行在不確定的狀態(tài)下.
- 使用新的等待/通知機制來代替它們.
1.2.4 安全地終止線程
- 通過標識位或中斷操作的方式, 能夠使線程在終止時有機會去清理資源. 而不是武斷地終止線程.
- 中斷是簡便的線程間交互方式, 適合于取消或停止任務.
- 同時, 利用一個boolean 變量來控制是否需要停止任務并終止進程.
// class Runner implements Runnable
public void run(){
while (on && !Thread.currentThread().isInterrupted(){
// do your job.
}
}
public void cancel(){
on = false;
}
// main method
thread.interrupt();
runner.cancel();
1.3 線程間通信
1.3.1 volatile/synchronized 關鍵字
1.3.2 等待/通知機制
- 生產(chǎn)者/消費者模型, 在功能層面上解耦了How & What.
- 循環(huán)檢查預期的方案
- 難以同時保證及時性和降低開銷. 循環(huán)的sleep 時長很難把握.
- 等待/通知機制依托于同步機制, 其目的是確保等待線程從wait()方法返回時能夠感知到通知線程對變量做出的修改.
- 使用wait()/notify()/notifyAll()時需要先對調(diào)用對象加鎖.
- wait(): 放棄鎖并進入對象的WaitQueue中, 進入Waiting狀態(tài).
- notify(): 將WaitThread 從WaitQueue 移到SynchronizedQueue中, 并將其狀態(tài)變?yōu)锽locked狀態(tài). 在notifyThread 釋放了鎖后, WaitThread 再次獲取到鎖并從wait() 返回并繼續(xù)執(zhí)行.
- 經(jīng)典范式:
// >>>> 等待方
synchronized(對象){
while(條件不滿足){
對象.wait();
}
對應的處理邏輯.
}
// >>>> 通知方
synchronized(對象){
改變條件
對象.notifyAll();
}
1.3.3 管道輸入/輸出流
- 與普通的I/O流的不同: 以內(nèi)存為傳輸媒介, 主要用于線程之間的數(shù)據(jù)傳輸.
- 有面向字節(jié)/字符的PipedInput/OutputStream, PipedReader/Writer.
- 必須先調(diào)用connect() 進行綁定, 才能進行訪問.
1.3.4 Thread.join() 的使用
- 每個join線程等待前驅(qū)線程終止后, 才從join()返回.
public final synchronized void join(){
//條件不滿足, 繼續(xù)等待
while(isAlive()){
wait(0);
}
//條件符合, 方法返回.
}
- 當線程終止時,會調(diào)用自身的notifyAll(), 來通知所有等待在該線程對象上的線程.
1.3.5 ThreadLocal 的使用
- 線程變量: 以ThreadLocal 對象為鍵, 任意對象為值的存儲結(jié)構(gòu).
- 該結(jié)構(gòu)被附帶在線程上.
1.4 生產(chǎn)者和消費者模式
使用阻塞隊列(容器)做第三方來解耦生產(chǎn)者和消費者, 兩者通過阻塞隊列進行通信.
- 線程池其實就是一種生產(chǎn)者消費者模式.
- 線程池的優(yōu)勢是在消費者能夠處理的場景(將要運行的任務數(shù)小于等于線程池的基本任務數(shù)時), 直接就將任務處理掉了.
- 比原生的生產(chǎn)者先存, 消費者再取的模式更快.