Concurrent Basic
本文引自《Java多線程編程實戰指南(設計模式篇)》
基礎
無處不在的線程
進程(Process)代表運行中的程序。一個運行的Java程序就是一個進程。
從操作系統地角度來看,線程(Thread)是進程中可獨立執行的子任務。一個進程可以包含多個線程。同一個進程中的線程共享該進程所申請到的資源,入內存空間和文件句柄。
從JVM的角度來看,線程是進程中的一個組件(Component),它可以看作執行Java代碼的最小單位。Java程序中任何一段代碼總是執行在某個確定的線程中。
JVM在啟動的時候會創建一個main線程,該線程負責執行Java程序的入口方法(main方法)。
在多線程編程中,弄清楚一段代碼具體由哪個(或者哪種)線程去負責執行的這點很重要,這關系到性能問題,線程安全問題等。
Java中的線程可以分為守護線程(Deamon Thread)和用戶線程(User Thread)。用戶線程會阻止JVM的正常停止,即JVM正常停止前應用程序中的所有用戶線程必須先停止完畢。否則JVM無法停止。而守護線程則不會影響JVM的正常停止,即應用程序中所有守護線程在運行也不影響JVM的正常停止。因此守護線程用來做一些重要性不是很高的任務,例如監視其他線程的運行情況。
線程的創建和運行
線程的狀態和上下文切換
Thread.State :
-
NEW
- 新建狀態。 -
RUNNABLE
- 該狀態是一個復合狀態。包括兩個子狀態:READY
和RUNNING
。 -
BLOCKED
- 一個線程發起一個阻塞式I/O(Blocking I/O)操作后,或者試圖去獲得一個由其他線程持有的鎖時,相應的線程會處于該狀態。處于該狀態的線程不會占用CPU資源。當相應的I/O操作完成后,或者相應的鎖被其他線程釋放后,該線程的狀態又可以轉換為RUNNABLE
。 -
WAITING
- 一個線程執行了某些方法之后就會處于這種無限等待其他線程執行特定操作的狀態。這些方法包括Object.wait(),Thread.join()和LockSupport.park()。能夠使相應的線程從WAITING
狀態轉換為RUNNABLE
狀態的相應方法包括:Object.notify(),Object.notifyAll()和LockSupport.unpark(thread); -
TIMED_WAITING
- 該狀態可WAITING
狀態類似,差別在于處于該狀態的線程并非無限等待其他線程執行特定操作,而是處于帶有時間限制的等待狀態。當其他線程沒有在指定時間內執行該線程所期望的特定操作時,該線程的狀態自動轉換為RUNNABLE
狀態。 -
TERMINATED
- 已經執行結束的線程處于該狀態。由于一個線程實例只能被啟動一次,因此一個線程也只可能有一次處于該狀態。Thread實例的run方法正常返回或者由于拋出異常而提前終止都會導致相應的線程處于該狀態。
從上述描述可知,一個線程在其整個生命周期中,只可能一次處于NEW
的狀態和TERMINATED
狀態。而一個線程的狀態從RUNNABLE狀態轉換為BLOCKED、WAITING和TIMED_WAITING這幾個狀態中的任何一個狀態都意味著上下文切換(Context Switch
)的產生。
上下文切換 :
對線程上下文信息進行保存和恢復的過程就稱之為上下文切換
。
線程的監視
jvisualvm
jstack
Java Mission Control
原子性、內存可見性、指令重排序、synchronized、volatile
原子操作
- Atomic,相應的操作時單一不可分割的操作。在多線程環境中,非原子操作可能會收到其他線程的干擾。synchronized
- 可以實現操作的原子性。其本質時通過該關鍵字所包括的臨界區(Critical Section)的排他性保證在任何一個時刻只有一個線程能夠執行臨界區內的代碼,這使得臨界區中的代碼代表了一個原子操作。synchronized
關鍵字代表的另外一個作用是內存的可見性(Memory Visibility),即保證了一個線程執行臨界區代碼時所修改的變量值對于稍后執行臨界區中的代碼的線程來說是可見的。臨界區
- Critical Section,示例代碼如下:
synchronized(syncObject) {
//critical section
}
內存可見性
- Memory Visibility,CPU在執行代碼的時候,為了減少變量訪問的時間消耗可能將代碼中訪問的變量的值緩存道該CPU的緩存區(如L1 Cache
,L2 Cache
等)中。因此,相應代碼再次訪問某個變量時,相應的值可能時從CPU緩存區而不是主內存中讀取的。同樣的,代碼對這些被緩存鍋的變量的值的修改也可能僅是被寫入CPU緩存區,而沒有被寫回主內存。由于每個CPU都有自己的緩存區,因此一個CPU混村去中的內容對于其他CPU而言是不可見的。這就導致了其他CPU上運行的其他線程可能無法“看到”該線程對某個變量值所做的更改。這就是所謂的內存可見性。volatile
- 保證內存可見性,但是不保證操作的原子性。其另一個作用是禁止了指令重排序
。指令重排序
- 編譯器和CPU為了提高指令的執行效率,可能會進行指令重排序操作。
synchronized
和volatile
的區別:synchronized
既能保證操作的原子性,又能保證內存的可見性。而volatile
僅能保證內存可見性。但是前者會導致上下文切換,而后者不會。
線程的優勢和風險
優勢 :
- 提高系統的吞吐率(
Throughput
) - 提高響應性(
Responsiveness
) - 充分利用多核(Muticore)CPU資源
- 最小化對系統資源的占用
- 簡化程序的結構
風險 :
- 線程安全問題
- 線程的生命特征問題
- 上下文切換
- 可靠性
常用術語
術語 | 釋義 |
---|---|
任務 Task | 把線程比作公司員工的,那么任務就可以被看作員工所要完成的工作內容。任務與線程并非一一對應的,通常一個線程可以用來執行多個任務。任務是一個相對的概念。一個文件可以被看作是一個任務,一個文件中的多個記錄可以被看作一個任務,多個文件也可以看作一個任務 |
并發 Concurrent | 表示多個任務同一時間段內被執行,這些任務并不是順序執行的,而往往是以交替的方式被執行 |
并行 Parallel | 表示多個任務在同一時刻被執行 |
客戶端線程 Client Thread | 從面向對象編程的角度來看,一個類總是對外提供某些服務(這也是這個類存在的意義)。其它類是通過調用該類的響應方法來使用這些服務的。因此,一個類的方法的調用方代碼就被稱為該類的客戶端代碼。相應地,執行客戶端代碼的線程就被稱為客戶端線程。因此,客戶端線程也是一個相對的概念,某個類的客戶端線程隨著執行該方法調用方代碼的線程的變化而變化 |
工作者線程 Worker Thread | 工作者線程是相對于客戶端線程而言的。它表示客戶端線程之外的用于特定用途的其他線程 |
上下文切換 Context Switch | 對線程的上下文信息進行保存和恢復的過程就稱為上下文切換 |
顯示鎖 Explicit Lock | 在Java代碼中可以使用和控制的鎖,即不是編譯器和CPU內部使用的鎖。包括synchronized 和java.util.concurrent.locks.Lock 接口的所有實現類 |
線程安全 Thread Safe | 一段操縱共享數據的代碼能夠保證在同一時間內被多個線程執行而保持其正確性的,就被稱為是線程安全的 |
———————————————————— 結束 ————————————————————