鎖的分類:
一耍攘,線程是否需要鎖住同步資源
廣義角度上的概念,對于同一個數(shù)據(jù)的并發(fā)操作下
-
樂觀鎖
- 認為自己使用數(shù)據(jù)的時候堪滨,不會有別的線程來修改數(shù)據(jù)胯陋,只是會在更新數(shù)據(jù)的時候,判斷數(shù)據(jù)是否被修改過袱箱,如果沒有修改過遏乔,則直接讀取數(shù)據(jù)操作,否則根據(jù)不同的實現(xiàn)方式來執(zhí)行不同的操作(重試或者報錯)发笔。
- 樂觀鎖常用無鎖編程來實現(xiàn)盟萨,最常采用的方法就是CAS算法,Java的原子類就是通過CAS算法來實現(xiàn)的了讨。
- 適合讀較多的場景
-
樂觀鎖的場景
AtomicInteger i = new AtomicInteger(1); i.incrementAndGet();
這里涉及到一個原子類AtomicInteger的CAS算法實現(xiàn)捻激,具體可以看這篇文章AtomicInteger實現(xiàn)原理
-
悲觀鎖
- 認為自己使用數(shù)據(jù)的時候,一定會有其他線程對數(shù)據(jù)進行了更新操作前计,所以獲取數(shù)據(jù)的時候一定要先加鎖铺罢,確保數(shù)據(jù)不會被修改。
- Java中的synchornized關(guān)鍵字和Lock的子類實現(xiàn)都是悲觀鎖残炮。
- 適合寫較多的場景韭赘。
悲觀鎖的場景-(需要保證多個線程使用的是同一個鎖)
方式一
private ReentrantLock lock = new ReentrantLock();
private void doLock() {
//嘗試獲取鎖
lock.lock();
//doSomeThing();
lock.unlock();
//執(zhí)行完unLock釋放鎖
}
方式二
private Object syncLock = new Object();
private void syncLock() {
//嘗試獲取鎖
synchronized (syncLock) {
doSomeThing();
}
//執(zhí)行完同步塊,自動釋放鎖
}
二势就,鎖住同步資源泉瞻,線程是否需要阻塞
- 阻塞
- 不阻塞
介紹自旋鎖之前,我們先說明一個概念苞冯,Java線程的阻塞和喚醒袖牙,都是需要操作系統(tǒng)來切換CPU狀態(tài)來實現(xiàn)的,這種狀態(tài)切換需要耗費處理器時間舅锄,如果同步塊的操作的內(nèi)容過于簡單鞭达,狀態(tài)轉(zhuǎn)換的耗時可能比執(zhí)行操作的時間更長
- 自旋鎖
- 不放棄CPU時間片,通過自旋等待鎖釋放皇忿,降低因CPU狀態(tài)切換的的消耗
- 自旋獲取成功之后畴蹭,操作同步資源
- 失敗的情況下,CPU切換狀態(tài)鳍烁,線程休眠
- 適應(yīng)性自旋鎖
- 意味這自旋的時間和次數(shù)都不在固定
- 而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定
- 如果對于同一個鎖對象上叨襟,自旋剛剛成功獲得過鎖,并且持有鎖的線程正在運行中幔荒,那么虛擬機就會認為這次自旋也是很有可能再次成功糊闽,進而它將允許自旋等待持續(xù)相對更長的時間
- 如果對于某個鎖梳玫,自旋很少成功獲得過,那在以后嘗試獲取這個鎖時將可能省略掉自旋過程右犹,直接阻塞線程提澎,避免浪費處理器資源。
三念链,多個線程競爭同步資源的流程細節(jié)有沒有區(qū)別
這四種鎖是指鎖的狀態(tài)盼忌,專門針對synchronized的
首先為什么Synchronized能實現(xiàn)線程同步?
- 無鎖
不鎖住資源钓账,多個線程只有一個能修改資源成功碴犬,其他的會重試(主要實現(xiàn)CAS算法) - 偏向鎖
同一個線程操作執(zhí)行同步資源時自動獲得資源 - 輕量級鎖
多個線程競爭同步資源時絮宁,沒有獲得資源的線程自旋或者等待資源釋放 - 重量級鎖
多個線程競爭同步資源時梆暮,沒有獲得資源的線程阻塞等待喚醒
所以目前鎖一共有4種狀態(tài),級別從低到高依次是:無鎖绍昂、偏向鎖啦粹、輕量級鎖和重量級鎖。鎖狀態(tài)只能升級不能降級窘游。
四唠椭,多個線程競爭資源要不要排隊
-
公平鎖 需要排隊
- 多線程根據(jù)申請鎖的順序來獲取鎖
- 優(yōu)點是等待鎖的線程不會餓死
- 缺點是整體的吞吐效率相對非公平鎖要低
- 等待隊列的線程都需要CPU喚醒阻塞線程,開銷比較大
-
非公平鎖 先嘗試插隊忍饰,失敗了再排隊
- 申請鎖的線程贪嫂,先嘗試獲取鎖,失敗了再排隊
- 優(yōu)點是整體的吞吐效率比較好艾蓝,可以減少喚醒線程的開銷
- 缺點是有線程可能會餓死或者等很久才能獲得鎖
-
ReentrantLock 公平和非公平鎖的實現(xiàn)方式
ReentrantLock里面有一個內(nèi)部類Sync力崇,Sync繼承AQS(AbstractQueuedSynchronizer),添加鎖和釋放鎖的大部分操作實際上都是在Sync中實現(xiàn)的
-
Sync有公平鎖FairSync和非公平鎖NonfairSync兩個子類赢织,公平鎖和非公平鎖的實現(xiàn)區(qū)別就是
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
如上可以看出該方法主要做一件事情:主要是判斷當(dāng)前線程是否位于同步隊列中的第一個亮靴。如果是則返回true,否則返回false于置。
綜上茧吊,公平鎖就是通過同步隊列來實現(xiàn)多個線程按照申請鎖的順序來獲取鎖,從而實現(xiàn)公平的特性八毯。非公平鎖加鎖時不考慮排隊等待問題搓侄,直接嘗試獲取鎖,所以存在后申請卻先獲得鎖的情況话速。
五休讳,一個線程中的多個流程能否獲得同一把鎖
-
可重入鎖 :能 可重入鎖又名遞歸鎖
指在同一個線程在外層方法獲取鎖的時候,再進入該線程的內(nèi)層方法會自動獲取鎖(前提鎖對象得是同一個對象或者class)尿孔,不會因為之前已經(jīng)獲取過還沒釋放而阻塞俊柔。
Java中ReentrantLock和synchronized都是可重入鎖筹麸,
可重入鎖的一個優(yōu)點是可一定程度避免死鎖。
-
可重入鎖示例
private synchronized void doOne(String a) { doOnther(a); } private synchronized void doOnther(String b) { System.out.println("b = [" + b + "]"); }
非可重入鎖 :不能
六雏婶,多個線程能否共享一把鎖
- 共享鎖 :能
- 排它鎖 :不能
其他文章