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