參考:
《Java并發編程的藝術》第四章
《Java多線程編程核心技術》
博客 http://www.lxweimin.com/p/8a04b5ec786c Java多線程基礎
博客 http://www.lxweimin.com/p/12af2d966c13 Java并發編程1
一.線程簡介
1.線程和進程
- 進程是系統進行資源分配和調度的一個獨立單位,現代操作系統運行程序時會創建進程
- 線程也叫輕量級進程,是現代操作系統調度的最小單元,一個進程中可以有多個線程,每個線程都擁有各自的計數器、堆棧和局部變量等屬性,并能夠訪問共享的內存變量
2.為什么使用多線程
- 更多的計算核心:充分利用多核處理器的硬件優勢,將計算邏輯分配給多個處理器同時執行
- 更快的響應時間:在業務邏輯復雜的場景中,將數據一致性不強的操作派發給其他線程處理
- 更好的編程邏輯:Java提供了良好并且一致的多線程編程模型,方便開發者完成多線程開發
3.上下文切換
- CPU切換運行的線程時存儲和恢復CPU的過程,使得線程能夠從中斷點恢復執行
- 線程上下文切換過程中會記錄程序計數器、CPU寄存器狀態等
- 多線程環境中上下文切換會帶來一定的性能開銷
4.線程優先級
- 現代操作系統采取分時的形式調度執行的線程
- 線程在獲取操作系統分出的時間片后開始執行,時間片用完后停止執行,等待再次獲得時間片
- 線程優先級決定線程獲取CPU時間片的優先級
- 注意:Java線程優先級在某些操作系統中無效
5.線程的狀態
狀態名稱 | 說明 |
---|---|
NEW | 初始狀態,線程被構建,但還沒有調用start() 方法 |
RUNNABLE | 運行狀態,包括線程在操作系統中就緒和運行兩種情況 |
BLOCKED | 阻塞狀態,表示線程阻塞于鎖 |
WAITING | 等待狀態,進入等待狀態的線程需要等待其他線程的特定動作(通知或中斷) |
TIME_WAITING | 超時等待狀態,進入超時等待狀態的線程可以在指定的時間自行返回 |
TERMINATED | 終止狀態,表示當前線程已經執行完畢 |
線程狀態切換
6. Daemon線程
- 支持型線程,用作程序中后臺調度以及支持型工作
- JVM不存在非Daemon線程時,Daemon線程將自動結束,JVM退出
- 注意,在JVM退出時,Daemon線程中的finally塊可能不會執行
二.線程啟動和終止
1.構造線程
-
void init(ThreadGroup g, Runnable target, String name,long stackSize,AccessControlContext acc)
方法完成線程構造 - 新構造的線程對象由其parent線程進行空間分配,繼承了parent線程的Daemon、優先級和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時獲得唯一的線程ID
2.線程的實現
- 繼承Thread類,重寫
run()
方法,Thread類本身實現了Runnable接口 - 實現Runnable接口,重寫
run()
方法 - 使用ExecutorService、Callable、Future實現有返回結果的多線程
3.線程中斷
- 一個線程應該自行停止,而非由其他線程強制中斷或停止
-
Thread.stop()
不保證資源的正確釋放、Thread.suspend()
暫停時不釋放鎖容易導致死鎖、Thread.resume()
等三個方法都已廢棄 - 每個線程均有一個中斷標志位,表示是否有其他線程對該線程進行了中斷操作
- 當對一個線程調用
interrupt()
方法時
1)如果線程處于等待狀態(如sleep、wait、join)時,線程將立即退出等待狀態,并拋出一個interruptedException異常,僅此而已
2)如果線程處于正常活動狀態,會將該線程的中斷標志設置為true,僅此而已。被設置中斷標志的線程將繼續正常運行,不受影響 - 所以
interrupt()
并不能真正的中斷線程,需要被調用的線程進行配合。如果一個線程有被中斷的需求,可以這樣做
1)在正常運行任務時,使用isInterrupted()
方法經常檢查本線程的中斷標志位,如果被設置了中斷標志就自行停止
2)線程處于等待狀態時,catch到InterruptedException異常后退出線程 -
Thread.interrupted()
方法將清除中斷標志位,但并不代表線程又恢復,僅代表線程已響應該中斷信號然后重置為可再次接收信號的狀態 - 處于等待的線程在調用
interrupt()
方法后拋出InterruptedException前,JVM將先清除線程的中斷標志位
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支持多個線程同時訪問共享對象,現代多核處理器為了加速程序運行,每個線程會擁有共享對象的一份拷貝,由此引出內存可見性問題——一個線程看到的變量并一定是最新的
-
volatile
:修飾的字段(成員變量),要求程序對該變量的訪問必須從共享內存獲取,修改必須同步刷新回共享內存,從而保證所有線程對變量訪問的可見性 -
synchronized
:修飾方法或同步塊,確保同一時刻,只有一個線程處于方法或同步塊中,保證了線程對變量訪問的可見性和排他性 - 對象、對象的監視器、同步隊列和執行線程之間的關系
1)任意線程對由synchronized保護的object的訪問,首先要獲得object的監視器Monitor
2)如果Monitor獲取失敗,線程進入同步隊列SynchronizedQueue,線程為BLOCKED狀態
3)當其他獲得鎖訪問object的線程釋放鎖,該釋放操作喚醒阻塞在同步隊列中的線程,使其重新嘗試獲取object的Monitor
2.等待/通知機制
- 生產者消費者模式
1)線程A修改了一個對象的值,線程B在感知變化后進行相應的操作
2)整個過程開始于線程A(生產者),最終執行于線程B(消費者)
3)該模式隔離了“做什么”(What)和“怎么做”(How),功能層面實現了解耦 - 原始辦法
消費者線程不斷循環檢查信號變量是否變化,偽代碼如下,存在程序及時性和資源消耗量的兩難
while ( value != desire ) {
Thread.sleep ( 1000 ) ;
}
doSometing();
等待/通知機制 notify/wait
1)線程A調用對象O的wait()
方法進入等待狀態
2)線程B調用對象O的notify()
或notifyAll()
方法通知線程A
3)線程A收到通知后從對象O的wait()
方法返回,繼續執行后續操作-
等待/通知機制流程
1)使用wait()
、notify()
、notifyAll()
時需要先對調用對象加鎖
2)調用wait()
后,線程狀態由RUNNING變為WAITING,并將當前線程放置在等待隊列
3)notify()
或notifyAll()
方法調用后,等待線程依舊不會從wait()
返回,需要調用notify()
或notifyAll()
的線程釋放鎖之后,等待線程才會從wait()
返回
4)notify()
/notifyAll()
將等待隊列中的一個/全部等待線程從等待隊列中移到同步隊列,被移動的線程狀態由WAITING變為BLOCKED
5)從wait()
方法返回(離開同步隊列開始運行)的前提是獲得了調用對象的鎖
Wait/Notify 等待/通知的經典范式
等待方偽代碼
1.獲取對象的鎖
2.如果條件不滿足,則調用對象的wait()方法,被通知后仍要檢查條件
3.條件滿足則執行對應邏輯
synchronized(對象) {
while(條件不滿足) {
對象.wait();
}
對應的處理邏輯
}
通知方偽代碼
1.獲取對象的鎖
2.改變條件
3.通知所有等待在對象上的線程
synchronized(對象) {
改變條件
對象.notifyAll();
}
3.管道輸入/輸出流
- 管道IO主要用于線程間數據傳輸,傳輸媒介為內存,與文件IO或網絡IO不同
- 主要實現類
1)管道字節流:PipedOutputStream
、PipedInputStream
2)管道字符流:PipedReader
、PipedWriter
PipedReader in = new PipedReader();
PipedWriter out = new PipedWriter();
//必須連接輸入流和輸出流,否則拋出異常
out.connect(in);
4.Thread.join()
- 線程A使用thread.join()表示當前線程A等待thread線程終止后才從thread.join()返回
-
join()
方法的源代碼邏輯結構與等待/通知經典范式一致,即加鎖、循環和處理邏輯三步
5.線程變量ThreadLocal
- 以ThreadLocal對象為鍵、任意對象為值得存儲結構,依附于線程
- 線程可以根據一個ThreadLocal對象查詢到綁定在這個線程上的一個值
那年離別日,只道住桐廬。桐廬人不見,今得廣州書