您好,登錄后才能下訂單哦!
從本課開(kāi)始學(xué)習(xí)并發(fā)編程的內(nèi)容。主要介紹并發(fā)編程的基礎(chǔ)知識(shí)、鎖、內(nèi)存模型、線程池、各種并發(fā)容器的使用。
并發(fā)編程
并發(fā)基礎(chǔ)
鎖
AQS
Synchronized
Lock
這小節(jié)咱們來(lái)學(xué)習(xí)并發(fā)編程中鎖的知識(shí)。主要包括關(guān)鍵字synchronized
、各種Lock
、AQS
的原理、以及各自的應(yīng)用。
synchronized
可以修飾方法或者代碼塊
表示多個(gè)線程訪問(wèn)該方法或者代碼塊時(shí)要進(jìn)行排隊(duì),串行的執(zhí)行該方法或者代碼塊
執(zhí)行效率低,但是它是并發(fā)編程容器的基礎(chǔ)
分類(lèi) | 具體分類(lèi) | 被鎖的對(duì)象 | 示例代碼 | 說(shuō)明 |
---|---|---|---|---|
方法 | 實(shí)例方法 | 類(lèi)的實(shí)例對(duì)象 | synchronized void methodA() {};<br />void methodB() {};<br />synchronized void methodC() {}; | 線程調(diào)用了同步方法,<br />別的線程可以調(diào)用非同步方法,<br />對(duì)于其他同步方法,必須該方法在執(zhí)行完成后才能調(diào)用<br />不影響靜態(tài)方法的調(diào)用(包括同步,非同步) |
靜態(tài)方法 | 類(lèi)對(duì)象 | static synchronized void methodA() {};<br />static void methodB() {};<br />static synchronized void methodC() {}; | 線程調(diào)用了同步方法,<br />別的線程可以調(diào)用非同步方法,<br />對(duì)于其他同步方法,必須該方法在執(zhí)行完成后才能調(diào)用<br />不影響對(duì)象方法的調(diào)用(包括同步,非同步) | |
代碼塊 | 實(shí)例對(duì)象 | 類(lèi)的實(shí)例對(duì)象 | synchronized(this) {} | 同上 |
class對(duì)象 | 類(lèi)對(duì)象 | synchronized(SynchronizedTest.class) {} | 同上 | |
任意實(shí)例對(duì)象 | 實(shí)例對(duì)象Object | Object lock = new Object();<br />synchronized(lock) {} | 只影響鎖住的對(duì)象,而不影響類(lèi)和類(lèi)的實(shí)例對(duì)象 |
synchronized
的實(shí)現(xiàn)機(jī)制JAVA對(duì)象頭和Monitor是實(shí)現(xiàn)synchronized
的基礎(chǔ)。
JAVA對(duì)象頭,對(duì)于Hotspot虛擬機(jī)的對(duì)象頭主要包含兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)、Klass Pointer(類(lèi)型指針)
Monitor,是一種同步機(jī)制,即同一時(shí)刻只允許一個(gè)線程進(jìn)入Monitor的臨界區(qū),從而達(dá)到互斥的效果。synchronized
的對(duì)象鎖,其指針指向的是一個(gè)Monitor對(duì)象的起始地址。每個(gè)對(duì)象實(shí)例都有一個(gè)monitor。由C++實(shí)現(xiàn),其數(shù)據(jù)結(jié)構(gòu)如下。
```C++
ObjectMonitor() {
_count = 0;
_owner = NULL;
_waitSet = NULL;
_waitSetLock = 0;
_EntryList = NULL;
}
其中,**_owner**指向持有ObjectMonitor對(duì)象的線程。當(dāng)多個(gè)線程同時(shí)訪問(wèn)一段同步代碼時(shí),會(huì)把線程存放在鎖的對(duì)象的**_EntryList**中。當(dāng)某個(gè)線程獲得對(duì)象的Monitor時(shí),就會(huì)把*_owner*的值設(shè)置為當(dāng)前線程,同時(shí)*_count*加1。如果線程調(diào)用**wait()**方法,就會(huì)釋放當(dāng)前持有的Monitor,*_owner*置為null,*_count*減1,并將該線程放入**_waitSet**中。當(dāng)然,如果持有monitor的線程正常執(zhí)行完畢,也會(huì)釋放monitor,*_owner*置為null,*_count*減1。
對(duì)于加在代碼塊上的synchronized
,其字節(jié)碼是:一次monitorenter
、兩次monitorexit
(含有一次編譯器自動(dòng)生成的異常處理的monitorexit
);
對(duì)于加在方法上的synchronized
,其字節(jié)碼是:標(biāo)識(shí)方法為ACC_SYNCHRONIZED
synchronized
是一個(gè)重量級(jí)鎖,相較Lock,比較笨重,不高效。在JDK1.6中,其實(shí)現(xiàn)過(guò)程引入了大量的優(yōu)化,如自旋鎖、自適應(yīng)自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級(jí)鎖等技術(shù)來(lái)減少鎖的開(kāi)銷(xiāo)。
自旋鎖,是指當(dāng)一個(gè)線程在獲取鎖的時(shí)候,如果鎖已經(jīng)被其他線程獲取,那么該線程將循環(huán)等待,然后不斷判斷鎖是否能夠成功獲取,直到獲取到鎖才退出循環(huán)。
優(yōu)點(diǎn):自旋鎖不會(huì)使線程發(fā)生狀態(tài)切換,而是一直處于活動(dòng)狀態(tài),不會(huì)進(jìn)入阻塞狀態(tài),減少了不必要的上下文切換,執(zhí)行速度快
缺點(diǎn):如果某個(gè)線程持有鎖的時(shí)間過(guò)長(zhǎng),就會(huì)導(dǎo)致其他等待獲取鎖的線程進(jìn)入循環(huán)等待,消耗CPU。如果使用不當(dāng)會(huì)導(dǎo)致CPU使用率極高;不公平的鎖會(huì)導(dǎo)致“線程饑餓”問(wèn)題
自適應(yīng)自旋鎖,JDK1.6引入的更聰明的自旋鎖。就是說(shuō)自旋的次數(shù)不是固定的,它是由前一次在同一個(gè)鎖上的自旋時(shí)間以及鎖的持有者的狀態(tài)決定的。JVM自適應(yīng)的調(diào)整自旋次數(shù),使能更有效的獲取到鎖,避免浪費(fèi)資源
鎖消除,當(dāng)JVM檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng),此時(shí)會(huì)對(duì)這些鎖進(jìn)行鎖消除
鎖粗化,在使用鎖時(shí),需要讓同步代碼塊的作用范圍盡可能小。所謂鎖粗化,就是將多個(gè)連續(xù)的加鎖、解鎖操作連接在一起,擴(kuò)展成一個(gè)范圍更大的鎖
偏向鎖,是指同一段代碼一直被同一個(gè)線程訪問(wèn),那么該線程會(huì)自動(dòng)獲取到鎖,從而降低獲取鎖的代價(jià)
輕量級(jí)鎖,當(dāng)鎖是偏向鎖的時(shí)候,此時(shí)被另一個(gè)線程訪問(wèn),偏向鎖會(huì)升級(jí)為輕量級(jí)鎖。其他線程會(huì)通過(guò)自旋的形式嘗試獲取鎖,不會(huì)阻塞
synchronized
的實(shí)現(xiàn)依次是:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)
Lock是一個(gè)接口,有以下方法。
public interface Lock {
void lock();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
void lockInterruptibly();
Condition newCondition();
}
這里說(shuō)下方法void lockInterruptibly()
,一個(gè)線程獲得鎖之后是不可以被interrupt()
方法中斷的,是不能中斷正在執(zhí)行中的線程的,只能中斷阻塞過(guò)程中的線程,lockInterruptibly
方法允許當(dāng)線程等待獲取鎖的過(guò)程中由其他線程來(lái)中斷等待。
區(qū)別:
相同點(diǎn):
可重入鎖,是Lock接口的唯一實(shí)現(xiàn)類(lèi)。
可重入鎖:是指如果一個(gè)線程獲得了一個(gè)對(duì)象的鎖,那么它不需要再獲取該對(duì)象的鎖而可以直接執(zhí)行方法。也就是鎖的分配機(jī)制是基于線程來(lái)分配的,而不是基于方法調(diào)用的分配。
可中斷鎖:可以響應(yīng)中斷的鎖。只有lockInterruptibly()
方法的鎖是可中斷鎖,lock()
還是不可中斷的
公平鎖:指盡量以請(qǐng)求鎖的順序來(lái)獲取鎖。比如有多個(gè)線程在等待一個(gè)鎖,當(dāng)鎖被其他線程釋放時(shí),最先請(qǐng)求鎖的線程會(huì)獲得該鎖
非公平鎖:無(wú)法保證鎖的獲取是按照請(qǐng)求鎖的順序進(jìn)行的??赡軐?dǎo)致某個(gè)或者一些線程永遠(yuǎn)獲取不到鎖
對(duì)于ReentrantLock
,默認(rèn)是非公平鎖,但可指定為公平鎖。
ReentrantLock lock = new ReentrantLock(true)
在ReentrantLock
中定義了兩個(gè)內(nèi)部類(lèi),一個(gè)是NotFairSync
,一個(gè)是FairSync
。當(dāng)構(gòu)造器參數(shù)為true時(shí),表示創(chuàng)建公平鎖,參數(shù)為false或者無(wú)參時(shí),表示非公平鎖。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
一個(gè)用來(lái)獲取讀鎖,一個(gè)用來(lái)獲取寫(xiě)鎖。
可重入讀寫(xiě)鎖,實(shí)現(xiàn)了ReadWriteLock
接口。
多個(gè)線程同時(shí)進(jìn)行讀操作時(shí),會(huì)使多個(gè)線程交替進(jìn)行,從而提高讀操作的效率。但是,如果有線程占用讀鎖,此時(shí)其他要獲取寫(xiě)鎖的話,就必須等待讀鎖釋放后才可執(zhí)行;如果有線程占用寫(xiě)鎖,此時(shí)其他線程不管是要獲取讀鎖或者寫(xiě)鎖的話,都必須等待寫(xiě)鎖釋放。
ReentrantLock
的FairSync
和NotFairSync
都繼承了AbstractQueuedSynchronizer
,并且真正lock()
和unlock()
的實(shí)現(xiàn)過(guò)程都是在AQS中。
首先,AQS的數(shù)據(jù)結(jié)構(gòu)是:一個(gè)表示鎖狀態(tài)的變量volatile int state
,取值范圍是 0 無(wú)鎖、1 有鎖;一個(gè)用于存儲(chǔ)等待獲取鎖的線程的雙向鏈表transient volatile Node head
和transient volatile Node tail
。
其次,加鎖流程NotFairSync.lock()
是:
通過(guò)CAS去嘗試獲取鎖:判斷當(dāng)前state是0的話表示無(wú)鎖,然后把當(dāng)前線程設(shè)置為獨(dú)占執(zhí)行線程,再修改state為1表示有鎖
acquire(1)
(這是AQS的方法)主要是三個(gè)方法:
tryAcquire
,再次嘗試通過(guò)CAS獲取一次鎖(子類(lèi)NotFairSync
的方法)
addWaiter
,把當(dāng)前線程放入等待隊(duì)列的雙向鏈表Node中(通過(guò)無(wú)限循環(huán)-自旋,找到鏈表尾,接到尾部)acquireQueued
,通過(guò)自旋,判斷當(dāng)前線程是否到達(dá)鏈表頭部,當(dāng)?shù)竭_(dá)頭部時(shí),就嘗試獲取鎖。如果獲取到鎖就把其從頭部移除
shouldParkAfterFailedAcquire
判斷當(dāng)前線程的狀態(tài)是Cancelled
、Signal
等,從而在parkAndCheckInterrupt
中對(duì)線程進(jìn)行剔除(Cancelled)、阻塞(Signal)操作
下面借兩張圖(<https://yq.aliyun.com/articles/640868>)
cdn.com/71e8b71038243dfaf21ebcf6f9fcc5fbaa659b08.png">
獲取鎖的流程:
然后,解鎖的流程是調(diào)用NotFairSync.release()
,主要是對(duì)重入數(shù)量的調(diào)整。每次釋放鎖都只會(huì)對(duì)數(shù)量減1,直到state為0時(shí)表示鎖釋放完成。
根據(jù)CAS的特性,建議在低鎖沖突的情況下使用Lock
在JDK1.6后,官方對(duì)synchronized做了大量的優(yōu)化(偏向鎖、自旋、輕量級(jí)鎖等),因此在非必要的情況下,建議都使用synchronized做同步操作
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。