線程和進程
進程是操作系統(tǒng)資源分配的基本單位,而線程是任務調度和執(zhí)行的基本單位,每個進程都有獨立的代碼和數(shù)據(jù)空間(進程上下文),進程切換的開銷大知牌。
線程:輕量的進程,同一類線程共享代碼和數(shù)據(jù)空間,每個線程有獨立的運行棧和程序計數(shù)器(PC),線程切換的開銷小丹拯。一個線程是一個程序內(nèi)部的順序控制流站超。
多進程:同時運行多個任務(程序)。
多線程:在同一應用程序中有多個順序流同時執(zhí)行乖酬。
線程的概念模型
●虛擬的CPU,封裝在 java. lang. Thread類中死相。
●CPU所執(zhí)行的代碼,傳遞給 Thread類。
●CPU所處理的數(shù)據(jù),傳遞給 Thread類咬像。
●Java的線程是通過 java. lang. Thread類來實現(xiàn)的算撮。、
●每個線程都是通過某個特定 Thread對象的方法run()來完成其操作的方法run()稱為線程體县昂。
構造線程的三種方法
定義一個線程類,它繼承類 Thread并重寫其中的方法run()肮柜;
提供一個實現(xiàn)接口 Runnable的類作為線程的目標對象,在初始化;
通過Callable和Future創(chuàng)建線程七芭。
一個 Thread類或者 Thread子類的線程對象時,把目標對象傳遞給這個線程實例,由該目標對象提供線程體run()素挽。
main線程已經(jīng)執(zhí)行完后,新線程才執(zhí)行完,main方法調用 thread. start()方法啟動新線程后并不等待其run方法返回就繼續(xù)運行,線程的run方法在一邊獨自運行,不影響原來的main方法的運行。
Runnable接口
只有一個run()方法,Thread類實現(xiàn)了 Runnable接囗,便于多個線程共享資源.
□Java不支持多繼承,如果已經(jīng)繼承了某個基類,便需要
現(xiàn) Runnable接口來生成多線程
口以實現(xiàn) Runnable的對象為參數(shù)建立新的線程
口sta方法啟動線程就會運行run(方法
多線程的同步控制
●有時線程之間彼此不獨立狸驳、需要同步
口線程間的互斥
同時運行的幾個線程需要共享一個(些)數(shù)據(jù)
共享的數(shù)據(jù),在某一時刻只允許一個線程對其進行操作
“生產(chǎn)者/消費者”問題
假設有一個線程負責往數(shù)據(jù)區(qū)寫數(shù)據(jù),另一個線程從同一數(shù)據(jù)
區(qū)中讀數(shù)據(jù),兩個線程可以并行執(zhí)行
如果數(shù)據(jù)區(qū)已滿,生產(chǎn)者要等消費者取走一些數(shù)據(jù)后才能再寫
當數(shù)據(jù)區(qū)空時,消費者要等生產(chǎn)者寫入一些數(shù)據(jù)后再取
●線程同步
口互斥:許多線程在同一個共享數(shù)據(jù)上操作而互不干擾,同一時刻
只能有一個線程訪問該共享數(shù)據(jù)预明。因此有些方法或程序段在同
時刻只能被一個線程執(zhí)行,稱之為監(jiān)視區(qū)
口協(xié)作:多個線程可以有條件地同時操作共享數(shù)據(jù)缩赛。執(zhí)行監(jiān)視區(qū)代
碼的線程在條件滿足的情況下可以允許其它線程進入監(jiān)視區(qū)
synchronized-一線程同步關鍵字,實現(xiàn)互斥
口用于指定需要同步的代碼段或方法,也就是監(jiān)視區(qū)
口可實現(xiàn)與一個鎖的交互。例如
synchronized(對象)(代碼段
口 synchronized的功能是:首先判斷對象的鎖是否在,如果在就獲得鎖
然后就可以執(zhí)行緊隨其后的代碼段;如果對象的鎖不在(已被其他
線程拿走),就進入等待狀態(tài),直到獲得鎖
口當被 synchronized限定的代碼段執(zhí)行完,就釋放鎖
●后臺線程
口也叫守護線程,通常是為了輔助其它線程而運行的線程
口它不妨礙程序終止
口一個進程中只要還有一個前臺線程在運行,這個進程就不會結束;如
果一個進程中的所有前臺線程都已經(jīng)結束,那么無論是否還有未結束
的后臺線程,這個進程都會結束
口“垃圾回收”便是一個后臺線程
口如果對某個線程對象在啟動(調用stat方法)之前調用了
setDaemon(true方法,這個線程就變成了后臺線程
sleep是線程類(Thread)的方法撰糠,導致此線程暫停執(zhí)行指定時間酥馍,給執(zhí)行機會給其他線程,但是監(jiān)控狀態(tài)依然保持阅酪,到時后會自動恢復旨袒。調用sleep不會釋放對象鎖。 wait是Object類的方法术辐,對此對象調用wait方法導致本線程放棄對象鎖砚尽,進入等待此對象的等待鎖定池,只有針對此對象發(fā)出notify方法(或notifyAll)后本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態(tài)辉词。
誕生狀態(tài)
口線程剛剛被創(chuàng)建
就緒狀態(tài)
口線程的 start方法已被執(zhí)行
口線程已準備好運行
運行狀態(tài)
口處理機分配給了線程,線程正在運行
阻塞狀態(tài)( Blocked)
口在線程發(fā)出輸入/輸出請求且必須等待其返回
口遇到用 synchronized標記的方法而未獲得鎖
口為等候一個條件變量,線程調用wat(方法
●休眠狀態(tài)( Sleeping)
口執(zhí)行seep方法而進入休眠
死亡狀態(tài)
口線程已完成或退出
線程調度
口在單CPU的系統(tǒng)中,多個線程需要共享CPU,在任何時間點
上實際只能有一個線程在運行
口控制多個線程在同一個CPU上以某種順序運行稱為線程調度
Java虛擬機支持一種非常簡單的必孤、確定的調度算法,叫做固
定優(yōu)先級算法。這個算法基于線程的優(yōu)先級對其進行調度
考慮這些線程在運行時環(huán)境下的調度和交替執(zhí)行,也
不需要進行額外的同步,或者在調用方進行任何其他
的協(xié)調操作,調用這個對象的行為都可以獲得正確的
結果,那這個對象是線程安全的瑞躺。
Java線程安全 互斥同步敷搪、非阻塞同步、無同步方案
互斥同步
●同步的互斥實現(xiàn)方式:臨界區(qū)( Critical Section),互斥量
( Mutex),信號量( Semaphore)
● Synchronized關鍵字:經(jīng)過編譯后,會在同步塊前后形成
monitorenter和 monitorexit兩個字節(jié)碼幢哨。
口(1) synchronized同步塊對自己是可重入的,不會將自己鎖死;
口(2)同步塊在已進入的線程執(zhí)行完之前,會阻塞后面其他線程的進入
采用 synchronized,重入鎖可實現(xiàn):等待可中斷赡勘、公平鎖、鎖
可以綁定多個條件
Synchronized表現(xiàn)為原生語法層面的互斥鎖,而 RenentrantLock表
現(xiàn)為API層面的互斥鎖
●阻塞同步:互斥同步存在的問題是進行線程阻塞和喚醒所帶來的性
能問題,這種同步稱為阻塞同步( Blocking Synchronization)捞镰。這是
種悲觀并發(fā)策略
●非阻塞同步:不同于悲觀并發(fā)策略,而是使用基于沖突檢測的樂觀
并發(fā)策略,就是先進行操作,如果沒有其他線程征用共享數(shù)據(jù),則
操作成功;否則就是產(chǎn)生了沖突,采取不斷重試直到成功為止的策
種策略不需要把線程掛起,稱為非阻塞同步
●使用硬件處理器指令進行不斷重試策略(DK15以后
口測試并設置( Test-and-set)
口獲取并增加( Fetch- and-Increment)
口交換(Swap)
口比較并交換( Compare-and-Swap,簡稱CAS)
口加載鏈接,條件存儲( Load-Linked, Store-conditional簡稱LLSC)
例:java實現(xiàn)類 AtomicInteger, AtomicDouble等等闸与。
●可重入代碼:也叫純代碼。相對線程安全來說,可以保證線程安全曼振。
可以在代碼執(zhí)行過程中斷它,轉而去執(zhí)行另一段代碼,而在控制權
返回后,原來的程序不會出現(xiàn)任何錯誤几迄。
●線程本地存儲:如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享,
那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個線程中執(zhí)行,如
果能保證,就可以把共享數(shù)據(jù)的可見范圍限定在同一個線程之內(nèi),
這樣無需同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭用問題。
鎖優(yōu)化(源自」DK6)
●自旋鎖
●自適應鎖
●鎖消除
●鎖粗化
偏向鎖
●互斥同步存在的問題:掛起線程和恢復線程都需要轉入內(nèi)核態(tài)中完
成,這些操作給系統(tǒng)的并發(fā)性能帶來很大的壓力
●自旋鎖:如果物理機器有一個以上的處理器能讓兩個或以上的線程
同時并行執(zhí)行,那就可以讓后面請求鎖的那個線程“稍等一會”,
但不放棄處理器的執(zhí)行時間,看看持有鎖的線程是否很快就會釋放
鎖冰评。為了讓線程等待,我們只需要讓線程執(zhí)行一個忙循環(huán)(自旋)
這項技術就是自旋鎖映胁。Java中自旋次數(shù)默認10次
自適應自旋
●自適應意味著鎖自旋的時間不再固定,而是由前一次在同一個鎖
上的自旋時間及鎖擁有者的狀態(tài)來決定。如果在同一個鎖對象上,
自旋等待剛剛成功獲得鎖,并且持有鎖的線程正在運行中,那么
虛擬機就會認為這次自旋也很有可能再次成功,進而它允許自旋
等待相對更長的一段時間甲雅。
鎖消除
●定義:JVM即時編譯器在運行時,對一些代碼上要求同步,但是
被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進行消除
●判定依據(jù):如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會逃逸
出去從而被其他線程訪問到,那就可以把它們當作棧上數(shù)據(jù)對待,
認為他們是線程私有的,同步加鎖自然無需進行解孙。
鎖粗化
通常我們的代碼總是將同步塊的作用范圍限制得盡量小,只在共享數(shù)
據(jù)的實際作用域中才進行同步,這樣是為了使得同步操作的數(shù)量盡可
能變小
●另一種情況是,如果一系列的連續(xù)操作都對同一個對象反復加鎖,甚至加
鎖操作是出現(xiàn)在循環(huán)體中,那即使沒有線程爭用,頻繁的進行互斥同步也
會導致不必要的性能損耗,此時只需要將同步塊范圍擴大即可。即:鎖粗
化
偏向鎖
●目的:消除數(shù)據(jù)無竟爭情況下的同步原語,進一步提高程序運行的性
能抛人。偏向鎖就是在無竟爭的情況下把整個同步都消除掉,連CAS操作
都不做
●偏向:意思是這個鎖會偏向于第一個獲得它的線程,如果在接下來的
執(zhí)行中,該鎖沒有被其他線程獲取,則持有偏向所得線程永遠不需要
再進行同步