參考:
《Java并發(fā)編程的藝術(shù)》第四章
《Java多線程編程核心技術(shù)》
博客 http://www.reibang.com/p/8a04b5ec786c Java多線程基礎(chǔ)
博客 http://www.reibang.com/p/12af2d966c13 Java并發(fā)編程1
一.線程簡(jiǎn)介
1.線程和進(jìn)程
- 進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位概而,現(xiàn)代操作系統(tǒng)運(yùn)行程序時(shí)會(huì)創(chuàng)建進(jìn)程
- 線程也叫輕量級(jí)進(jìn)程狈孔,是現(xiàn)代操作系統(tǒng)調(diào)度的最小單元浑玛,一個(gè)進(jìn)程中可以有多個(gè)線程陕贮,每個(gè)線程都擁有各自的計(jì)數(shù)器带污、堆棧和局部變量等屬性怔软,并能夠訪問共享的內(nèi)存變量
2.為什么使用多線程
- 更多的計(jì)算核心:充分利用多核處理器的硬件優(yōu)勢(shì)姜性,將計(jì)算邏輯分配給多個(gè)處理器同時(shí)執(zhí)行
- 更快的響應(yīng)時(shí)間:在業(yè)務(wù)邏輯復(fù)雜的場(chǎng)景中猪叙,將數(shù)據(jù)一致性不強(qiáng)的操作派發(fā)給其他線程處理
- 更好的編程邏輯:Java提供了良好并且一致的多線程編程模型赘风,方便開發(fā)者完成多線程開發(fā)
3.上下文切換
- CPU切換運(yùn)行的線程時(shí)存儲(chǔ)和恢復(fù)CPU的過程夹囚,使得線程能夠從中斷點(diǎn)恢復(fù)執(zhí)行
- 線程上下文切換過程中會(huì)記錄程序計(jì)數(shù)器、CPU寄存器狀態(tài)等
- 多線程環(huán)境中上下文切換會(huì)帶來一定的性能開銷
4.線程優(yōu)先級(jí)
- 現(xiàn)代操作系統(tǒng)采取分時(shí)的形式調(diào)度執(zhí)行的線程
- 線程在獲取操作系統(tǒng)分出的時(shí)間片后開始執(zhí)行邀窃,時(shí)間片用完后停止執(zhí)行荸哟,等待再次獲得時(shí)間片
- 線程優(yōu)先級(jí)決定線程獲取CPU時(shí)間片的優(yōu)先級(jí)
- 注意:Java線程優(yōu)先級(jí)在某些操作系統(tǒng)中無效
5.線程的狀態(tài)
狀態(tài)名稱 | 說明 |
---|---|
NEW | 初始狀態(tài),線程被構(gòu)建瞬捕,但還沒有調(diào)用start() 方法 |
RUNNABLE | 運(yùn)行狀態(tài)鞍历,包括線程在操作系統(tǒng)中就緒和運(yùn)行兩種情況 |
BLOCKED | 阻塞狀態(tài),表示線程阻塞于鎖 |
WAITING | 等待狀態(tài)肪虎,進(jìn)入等待狀態(tài)的線程需要等待其他線程的特定動(dòng)作(通知或中斷) |
TIME_WAITING | 超時(shí)等待狀態(tài)劣砍,進(jìn)入超時(shí)等待狀態(tài)的線程可以在指定的時(shí)間自行返回 |
TERMINATED | 終止?fàn)顟B(tài),表示當(dāng)前線程已經(jīng)執(zhí)行完畢 |
6. Daemon線程
- 支持型線程扇救,用作程序中后臺(tái)調(diào)度以及支持型工作
- JVM不存在非Daemon線程時(shí)刑枝,Daemon線程將自動(dòng)結(jié)束香嗓,JVM退出
- 注意,在JVM退出時(shí)装畅,Daemon線程中的finally塊可能不會(huì)執(zhí)行
二.線程啟動(dòng)和終止
1.構(gòu)造線程
-
void init(ThreadGroup g, Runnable target, String name靠娱,long stackSize,AccessControlContext acc)
方法完成線程構(gòu)造 - 新構(gòu)造的線程對(duì)象由其parent線程進(jìn)行空間分配洁灵,繼承了parent線程的Daemon饱岸、優(yōu)先級(jí)和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時(shí)獲得唯一的線程ID
2.線程的實(shí)現(xiàn)
- 繼承Thread類徽千,重寫
run()
方法苫费,Thread類本身實(shí)現(xiàn)了Runnable接口 - 實(shí)現(xiàn)Runnable接口,重寫
run()
方法 - 使用ExecutorService双抽、Callable百框、Future實(shí)現(xiàn)有返回結(jié)果的多線程
3.線程中斷
- 一個(gè)線程應(yīng)該自行停止,而非由其他線程強(qiáng)制中斷或停止
-
Thread.stop()
不保證資源的正確釋放牍汹、Thread.suspend()
暫停時(shí)不釋放鎖容易導(dǎo)致死鎖铐维、Thread.resume()
等三個(gè)方法都已廢棄 - 每個(gè)線程均有一個(gè)中斷標(biāo)志位,表示是否有其他線程對(duì)該線程進(jìn)行了中斷操作
- 當(dāng)對(duì)一個(gè)線程調(diào)用
interrupt()
方法時(shí)
1)如果線程處于等待狀態(tài)(如sleep慎菲、wait嫁蛇、join)時(shí),線程將立即退出等待狀態(tài)露该,并拋出一個(gè)interruptedException異常睬棚,僅此而已
2)如果線程處于正常活動(dòng)狀態(tài)解幼,會(huì)將該線程的中斷標(biāo)志設(shè)置為true抑党,僅此而已。被設(shè)置中斷標(biāo)志的線程將繼續(xù)正常運(yùn)行撵摆,不受影響 - 所以
interrupt()
并不能真正的中斷線程底靠,需要被調(diào)用的線程進(jìn)行配合。如果一個(gè)線程有被中斷的需求特铝,可以這樣做
1)在正常運(yùn)行任務(wù)時(shí)暑中,使用isInterrupted()
方法經(jīng)常檢查本線程的中斷標(biāo)志位,如果被設(shè)置了中斷標(biāo)志就自行停止
2)線程處于等待狀態(tài)時(shí)鲫剿,catch到InterruptedException異常后退出線程 -
Thread.interrupted()
方法將清除中斷標(biāo)志位痒芝,但并不代表線程又恢復(fù),僅代表線程已響應(yīng)該中斷信號(hào)然后重置為可再次接收信號(hào)的狀態(tài) - 處于等待的線程在調(diào)用
interrupt()
方法后拋出InterruptedException前牵素,JVM將先清除線程的中斷標(biāo)志位
Modifier and Type | Method | Description |
---|---|---|
void | interrupt() | Interrupts this thread |
static boolean | interrupted() | Tests whether the current thread has been interrupted |
boolean | isInterrupted() | Tests whether this thread has been interrupted |
三.線程間通信
1.volatile和synchronized
- Java支持多個(gè)線程同時(shí)訪問共享對(duì)象,現(xiàn)代多核處理器為了加速程序運(yùn)行澄者,每個(gè)線程會(huì)擁有共享對(duì)象的一份拷貝笆呆,由此引出內(nèi)存可見性問題——一個(gè)線程看到的變量并一定是最新的
-
volatile
:修飾的字段(成員變量)请琳,要求程序?qū)υ撟兞康脑L問必須從共享內(nèi)存獲取,修改必須同步刷新回共享內(nèi)存赠幕,從而保證所有線程對(duì)變量訪問的可見性 -
synchronized
:修飾方法或同步塊俄精,確保同一時(shí)刻,只有一個(gè)線程處于方法或同步塊中榕堰,保證了線程對(duì)變量訪問的可見性和排他性 - 對(duì)象竖慧、對(duì)象的監(jiān)視器、同步隊(duì)列和執(zhí)行線程之間的關(guān)系
1)任意線程對(duì)由synchronized保護(hù)的object的訪問逆屡,首先要獲得object的監(jiān)視器Monitor
2)如果Monitor獲取失敗圾旨,線程進(jìn)入同步隊(duì)列SynchronizedQueue,線程為BLOCKED狀態(tài)
3)當(dāng)其他獲得鎖訪問object的線程釋放鎖魏蔗,該釋放操作喚醒阻塞在同步隊(duì)列中的線程砍的,使其重新嘗試獲取object的Monitor
2.等待/通知機(jī)制
- 生產(chǎn)者消費(fèi)者模式
1)線程A修改了一個(gè)對(duì)象的值,線程B在感知變化后進(jìn)行相應(yīng)的操作
2)整個(gè)過程開始于線程A(生產(chǎn)者)莺治,最終執(zhí)行于線程B(消費(fèi)者)
3)該模式隔離了“做什么”(What)和“怎么做”(How)廓鞠,功能層面實(shí)現(xiàn)了解耦 - 原始辦法
消費(fèi)者線程不斷循環(huán)檢查信號(hào)變量是否變化,偽代碼如下谣旁,存在程序及時(shí)性和資源消耗量的兩難
while ( value != desire ) {
Thread.sleep ( 1000 ) ;
}
doSometing();
等待/通知機(jī)制 notify/wait
1)線程A調(diào)用對(duì)象O的wait()
方法進(jìn)入等待狀態(tài)
2)線程B調(diào)用對(duì)象O的notify()
或notifyAll()
方法通知線程A
3)線程A收到通知后從對(duì)象O的wait()
方法返回床佳,繼續(xù)執(zhí)行后續(xù)操作-
等待/通知機(jī)制流程
1)使用wait()
、notify()
榄审、notifyAll()
時(shí)需要先對(duì)調(diào)用對(duì)象加鎖
2)調(diào)用wait()
后砌们,線程狀態(tài)由RUNNING變?yōu)閃AITING,并將當(dāng)前線程放置在等待隊(duì)列
3)notify()
或notifyAll()
方法調(diào)用后瘟判,等待線程依舊不會(huì)從wait()
返回怨绣,需要調(diào)用notify()
或notifyAll()
的線程釋放鎖之后,等待線程才會(huì)從wait()
返回
4)notify()
/notifyAll()
將等待隊(duì)列中的一個(gè)/全部等待線程從等待隊(duì)列中移到同步隊(duì)列拷获,被移動(dòng)的線程狀態(tài)由WAITING變?yōu)锽LOCKED
5)從wait()
方法返回(離開同步隊(duì)列開始運(yùn)行)的前提是獲得了調(diào)用對(duì)象的鎖
等待/通知的經(jīng)典范式
等待方偽代碼
1.獲取對(duì)象的鎖
2.如果條件不滿足篮撑,則調(diào)用對(duì)象的wait()方法,被通知后仍要檢查條件
3.條件滿足則執(zhí)行對(duì)應(yīng)邏輯
synchronized(對(duì)象) {
while(條件不滿足) {
對(duì)象.wait();
}
對(duì)應(yīng)的處理邏輯
}
通知方偽代碼
1.獲取對(duì)象的鎖
2.改變條件
3.通知所有等待在對(duì)象上的線程
synchronized(對(duì)象) {
改變條件
對(duì)象.notifyAll();
}
3.管道輸入/輸出流
- 管道IO主要用于線程間數(shù)據(jù)傳輸匆瓜,傳輸媒介為內(nèi)存赢笨,與文件IO或網(wǎng)絡(luò)IO不同
- 主要實(shí)現(xiàn)類
1)管道字節(jié)流:PipedOutputStream
、PipedInputStream
2)管道字符流:PipedReader
驮吱、PipedWriter
PipedReader in = new PipedReader();
PipedWriter out = new PipedWriter();
//必須連接輸入流和輸出流茧妒,否則拋出異常
out.connect(in);
4.Thread.join()
- 線程A使用thread.join()表示當(dāng)前線程A等待thread線程終止后才從thread.join()返回
-
join()
方法的源代碼邏輯結(jié)構(gòu)與等待/通知經(jīng)典范式一致,即加鎖左冬、循環(huán)和處理邏輯三步
5.線程變量ThreadLocal
- 以ThreadLocal對(duì)象為鍵桐筏、任意對(duì)象為值得存儲(chǔ)結(jié)構(gòu),依附于線程
- 線程可以根據(jù)一個(gè)ThreadLocal對(duì)象查詢到綁定在這個(gè)線程上的一個(gè)值
那年離別日拇砰,只道住桐廬梅忌。桐廬人不見狰腌,今得廣州書