线程调度、公平锁和非公平锁、乐观锁和悲观锁、锁优化、重入锁
1. 線程調(diào)度
線程調(diào)度指的就是給線程分配使用處理器的過(guò)程。主要的調(diào)度方式有兩種:協(xié)同式調(diào)度和搶占式調(diào)度。
1.1 協(xié)同式調(diào)度
線程完成自己的任務(wù)之后主動(dòng)通知系統(tǒng)切換到另一個(gè)線程上。
優(yōu)點(diǎn):
實(shí)現(xiàn)簡(jiǎn)單,線程對(duì)于自己的切換是已知的,不存在線程同步的問(wèn)題;
缺點(diǎn):
如果一個(gè)線程一直阻塞占用處理器,則其他線程都會(huì)被阻塞。
1.2 搶占式調(diào)度
由系統(tǒng)來(lái)為線程分配使用處理器的時(shí)間片。
優(yōu)點(diǎn):
線程的執(zhí)行時(shí)間是系統(tǒng)可控的,不會(huì)出現(xiàn)一個(gè)線程導(dǎo)致整個(gè)進(jìn)程阻塞的問(wèn)題。
Java使用的線程調(diào)度就是搶占式調(diào)度。
2. 公平鎖與非公平鎖
公平鎖是多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的時(shí)間順序來(lái)一次獲得鎖(默認(rèn)的是不公平鎖);
非公平鎖就是一種獲取鎖的搶占機(jī)制,是隨機(jī)獲得鎖的,和公平鎖不一樣的就是先來(lái)的不一定先得到鎖。
3. 樂(lè)觀鎖和悲觀鎖
3.1 悲觀鎖
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀。
每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。
傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
3.2 樂(lè)觀鎖
樂(lè)觀鎖(Optimistic Lock), 顧名思義,就是很樂(lè)觀。
每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。
樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)如果提供類似于write_condition機(jī)制的其實(shí)都是提供的樂(lè)觀鎖。
樂(lè)觀鎖是基于沖突檢測(cè)的樂(lè)觀并發(fā)策略。
3.3 應(yīng)用
Synchronized原始采用的就是CPU悲觀鎖機(jī)制,即線程獲取的是獨(dú)占鎖。
- 獨(dú)占鎖意味著其他線程只能依靠阻塞來(lái)等待線程釋放鎖。
Lock采用的就是樂(lè)觀鎖方式。所謂樂(lè)觀鎖就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。樂(lè)觀鎖實(shí)現(xiàn)的機(jī)制就是CAS操作。
4. 鎖優(yōu)化
Java虛擬機(jī)團(tuán)隊(duì)在JDK1.5到JDK1.6之間對(duì)高效并發(fā)進(jìn)行了重要改進(jìn),有以下五種技術(shù),都是為了在線程之間更高效地共享數(shù)據(jù),以及解決競(jìng)爭(zhēng)問(wèn)題,從而提高程序的執(zhí)行效率。
這里的鎖優(yōu)化主要是指 JVM 對(duì) synchronized 的優(yōu)化。
?4.1 自旋鎖和自適應(yīng)鎖
互斥同步進(jìn)入阻塞狀態(tài)的開(kāi)銷都很大,應(yīng)該盡量避免。
在許多應(yīng)用中,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間。
自旋鎖的思想是讓一個(gè)線程在請(qǐng)求一個(gè)共享數(shù)據(jù)的鎖時(shí)執(zhí)行忙循環(huán)(自旋) 一段時(shí)間,如果在這段時(shí)間內(nèi)能獲得鎖,就可以避免進(jìn)入阻塞狀態(tài)。
自旋鎖雖然能避免進(jìn)入阻塞狀態(tài)從而減少開(kāi)銷,但是它需要進(jìn)行忙循環(huán)操作占用CPU 時(shí)間,它只適用于共享數(shù)據(jù)的鎖定狀態(tài)很短的場(chǎng)景。
在 JDK 1.6 中引入了自適應(yīng)的自旋鎖。自適應(yīng)意味著自旋的次數(shù)不再固定了,而是由前一次在同一個(gè)鎖上的自旋次數(shù)及鎖的擁有者的狀態(tài)來(lái)決定。
自旋是有限定的次數(shù)的,在JDK 1.6引入了自適應(yīng)自旋。
自適應(yīng)意味著自旋的時(shí)間不再固定了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間以及鎖的擁有者的狀態(tài)來(lái)決定。
4.2 鎖消除
鎖消除是指對(duì)于被檢測(cè)出不可能存在競(jìng)爭(zhēng)的共享數(shù)據(jù)的鎖進(jìn)行消除。
鎖消除主要是通過(guò)逃逸分析來(lái)支持,如果堆上的共享數(shù)據(jù)不可能逃逸出去被其它線程訪問(wèn)到,那么就可以把它們當(dāng)成私有數(shù)據(jù)對(duì)待,也就可以將它們的鎖進(jìn)行消除。
對(duì)于一些看起來(lái)沒(méi)有加鎖的代碼,其實(shí)隱式的加了很多鎖。例如下面的字符串拼接代碼就隱式加了鎖:
public static String concatString(String s1, String s2, String s3) {return s1 + s2 + s3; }String 是一個(gè)不可變的類,編譯器會(huì)對(duì) String 的拼接自動(dòng)優(yōu)化。在 JDK 1.5 之前,會(huì)轉(zhuǎn)化為 StringBuffer 對(duì)象的連續(xù) append() 操作:
public static String concatString(String s1, String s2, String s3) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);sb.append(s3);return sb.toString(); }每個(gè) append() 方法中都有一個(gè)同步塊。
虛擬機(jī)觀察變量 sb,很快就會(huì)發(fā)現(xiàn)它的動(dòng)態(tài)作用域被限制在 concatString() 方法內(nèi)部。
也就是說(shuō),sb 的所有引用永遠(yuǎn)不會(huì)逃逸到 concatString() 方法之外,其他線程無(wú)法訪問(wèn)到它,因此可以進(jìn)行消除。
4.3 鎖粗化
如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,頻繁的加鎖操作就會(huì)導(dǎo)致性能損耗。
上一節(jié)的示例代碼中連續(xù)的 append() 方法就屬于這類情況。
如果虛擬機(jī)探測(cè)到由這樣的一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖,將會(huì)把加鎖的范圍擴(kuò)展(粗化) 到整個(gè)操作序列的外部。
對(duì)于上一節(jié)的示例代碼就是擴(kuò)展到第一個(gè) append() 操作之前直至最后一個(gè) append() 操作之后,這樣只需要加鎖一次就可以了。
JDK 1.6 引入了偏向鎖和輕量級(jí)鎖,從而讓鎖擁有了四個(gè)狀態(tài):
- 無(wú)鎖狀態(tài)(unlocked)
- 偏向鎖狀態(tài)(biasble)
- 輕量級(jí)鎖狀態(tài)(lightweight locked)
- 重量級(jí)鎖狀態(tài)(inflated)
4.4 輕量級(jí)鎖
并不是用來(lái)代替重量級(jí)鎖的,它的本意是在沒(méi)有多線程競(jìng)爭(zhēng)的前提下減少傳統(tǒng)重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗。
輕量級(jí)鎖能提升程序同步性能的依據(jù)是“對(duì)于絕大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在競(jìng)爭(zhēng)的”。
在無(wú)競(jìng)爭(zhēng)的情況下使用CAS操作避免了使用互斥量的開(kāi)銷,但是如果存在鎖競(jìng)爭(zhēng),除了互斥量的開(kāi)銷外,還額外發(fā)生了CAS操作,因此在有競(jìng)爭(zhēng)的情況下,輕量級(jí)鎖會(huì)比傳統(tǒng)的重量級(jí)鎖更慢。
適用場(chǎng)景:
追求響應(yīng)時(shí)間,同步塊執(zhí)行速度非常快。
使用操作系統(tǒng)互斥量來(lái)實(shí)現(xiàn)的傳統(tǒng)鎖稱為是“重量級(jí)”鎖。
4.5 偏向鎖
目的是消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ),進(jìn)一步提高程序的運(yùn)行性能。偏向鎖會(huì)偏向于第一個(gè)獲得它的線程,如果在后面的執(zhí)行過(guò)程中,該鎖沒(méi)有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步。
偏向鎖可以提高帶有同步但無(wú)競(jìng)爭(zhēng)的程序性能。但是如果程序中大多數(shù)的鎖總是被多個(gè)不同的線程訪問(wèn),那偏向模式就是多余的。
適用場(chǎng)景:
只有一個(gè)線程訪問(wèn)同步塊場(chǎng)景。
5. ReentrantLock(可重入鎖)
可重入鎖的三個(gè)特性:
5.1 等待可中斷
當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候,正在等待的線程可以選擇放棄等待,改為處理其他事情。
可中斷特性對(duì)處理執(zhí)行時(shí)間長(zhǎng)的同步塊很有幫助。
5.2 公平鎖與非公平鎖
ReentrantLock默認(rèn)是非公平鎖,但是可以通過(guò)帶布爾值的構(gòu)造函數(shù)要求使用公平鎖。
公平鎖是多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的時(shí)間順序來(lái)一次獲得鎖(默認(rèn)的是不公平鎖);
非公平鎖就是一種獲取鎖的搶占機(jī)制,是隨機(jī)獲得鎖的,和公平鎖不一樣的就是先來(lái)的不一定先得到鎖。
5.3
鎖可以綁定多個(gè)條件condition,可以選擇性通知指定的某個(gè)種類的condition。
6.怎么檢查一個(gè)線程是否擁有鎖?
在java.lang.Thread中有一個(gè)方法叫holdsLock(),它返回true如果當(dāng)且僅當(dāng)當(dāng)前線程擁有某個(gè)具體對(duì)象的鎖。
7. Lock與synchronized的區(qū)別
7.1 是否為語(yǔ)言內(nèi)置:
synchronized是Java語(yǔ)言的關(guān)鍵字,因此是內(nèi)置特性。
Lock不是Java語(yǔ)言內(nèi)置的,是一個(gè)類,通過(guò)這個(gè)類可以實(shí)現(xiàn)同步訪問(wèn);
7.2 用法
synchronzied可以用于去修飾方法或代碼塊,而Lock則需要在代碼中顯示的通過(guò)調(diào)用lock()和unlock()方法來(lái)指定起始位置和終止位置。synchronize是托管給JVM的,而Lock的鎖定是通過(guò)代碼實(shí)現(xiàn)的,它有比synchronized更精確的線程語(yǔ)義。
7.3 鎖機(jī)制
Lock和synchronized有一點(diǎn)非常大的不同,
采用synchronized不需要用戶去手動(dòng)釋放鎖,當(dāng)synchronized方法或者synchronized代碼塊執(zhí)行完之后,系統(tǒng)會(huì)自動(dòng)讓線程釋放對(duì)鎖的占用;
而Lock則必須要用戶去手動(dòng)釋放鎖,如果沒(méi)有主動(dòng)釋放鎖,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象;
7.4 并發(fā)量對(duì)性能的影響
并發(fā)數(shù)量多的時(shí)候synchronized性能會(huì)下降很厲害,但是對(duì)Lock影響不大。
7.5 Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。
?
總結(jié)
以上是生活随笔為你收集整理的线程调度、公平锁和非公平锁、乐观锁和悲观锁、锁优化、重入锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 分页及其管理、页面置换算法
- 下一篇: 线程安全、守护线程、join()