根據(jù)不同的業(yè)務(wù)場(chǎng)景,選擇合適的鎖?
前言:剛開始我看到這個(gè)標(biāo)題的時(shí)候我感覺(jué)“很熟悉,但是又很陌生”,因?yàn)殒i是有效的解決并發(fā)情況下保證臨界資源操作原子性的有效手段之一。下面我就從我們幾個(gè)開發(fā)使用的角度來(lái)說(shuō)我們常用的鎖。
鎖可以解決什么問(wèn)題?
鎖可以解決并行執(zhí)行任務(wù)執(zhí)行過(guò)程中對(duì),共享數(shù)據(jù)順序訪問(wèn)、修改的場(chǎng)景。比如對(duì)同一個(gè)賬戶進(jìn)行并行扣款或者轉(zhuǎn)賬。下面我們展開討論下 synchronized 、ReetranLock 以及他們的使用。
synchronized
synchronized 是 JDK 提供的內(nèi)置鎖, 由 JVM 虛擬機(jī)內(nèi)部實(shí)現(xiàn),是基于 monitor 機(jī)制, 在 JDK 1.6 之后被優(yōu)化,會(huì)有一個(gè)鎖升級(jí)的過(guò)程,將鎖的狀態(tài)存儲(chǔ)到對(duì)象頭中。
鎖升級(jí)過(guò)程,默認(rèn)是無(wú)鎖狀態(tài),首先會(huì)進(jìn)行判斷,如果是沒(méi)有字段競(jìng)爭(zhēng)的情況下會(huì)使用偏向鎖,偏向鎖的本質(zhì)就是將當(dāng)前獲得鎖的線程 id 設(shè)置到共享數(shù)據(jù)的對(duì)象頭中。然后升級(jí)為輕量級(jí)鎖,輕量級(jí)鎖的本質(zhì)是通過(guò) CAS 來(lái)修改 MarkWord 來(lái)實(shí)現(xiàn)的。最后再升級(jí)為重量級(jí)鎖,我們可以通過(guò)操作系統(tǒng)的 monitor 依賴操作系統(tǒng)的 MutexLock(互斥鎖)來(lái)實(shí)現(xiàn)的 。
四種使用方式
- 在靜態(tài)方法上使用
- 在普通方法上使用
- 鎖定 this 狀態(tài)
- 鎖定靜態(tài)類
加鎖狀態(tài)記錄位置
對(duì)象加鎖,記錄在對(duì)象頭中,對(duì)象頭如下圖所示。
在運(yùn)行期間,Mark Word里面存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。Mark Word可能變?yōu)榇鎯?chǔ)以下4種數(shù)據(jù),如下圖所示
鎖的膨脹和升級(jí)
鎖的升級(jí)和膨脹時(shí)候不可逆轉(zhuǎn)的。
使用場(chǎng)景
JDK 在并發(fā)包中, 使用 synchroinzed 的地方有:
- ConcurrentHashMap (jdk 1.8)
- HashTable
ReetrantLock
ReetrantLock 開發(fā)作者是 Doug Lea ,從 JDK1.5 開始過(guò)后加入 JDK 的鎖,主要是通過(guò) QAS 的方式來(lái)實(shí)現(xiàn)的, 通過(guò) Unsafe 包提供的 CAS 操作來(lái)進(jìn)行鎖狀態(tài)(state)的競(jìng)爭(zhēng)。然后通過(guò) LockSupport.park(this). 進(jìn)行 park 住線程,如果在 AQS 隊(duì)列頭的對(duì)象進(jìn)行喚醒執(zhí)行 unpack 方法,然后讓他去競(jìng)爭(zhēng)鎖。
ReetrantLock 還分為公平鎖和非公平鎖,默認(rèn)是非公平鎖。因?yàn)楣芥i,是需要保證競(jìng)爭(zhēng)者按照獲取鎖的順序進(jìn)行獲得,性能略低于非公平鎖。
AQS 隊(duì)列結(jié)構(gòu)如下所示,它的本質(zhì)是一個(gè) FIFO 的線程安全的同步隊(duì)列,如下圖所示:
ReetrantLock 加鎖和解鎖的過(guò)程如下圖所示:
使用方式
ReetrantLock 的使用方式如下,主要是有三個(gè)步驟:創(chuàng)建、加鎖、解鎖。
- class X {
- private final ReentrantLock lock = new ReentrantLock();
- // ...
- public void m() {
- lock.lock(); // block until condition holds
- try {
- // ... method body
- } finally {
- lock.unlock()
- }
- }
- }
使用場(chǎng)景
JDK 在并發(fā)包中, 使用 ReetrantLock 的地方有:
- CyclicBarrier
- DelayQueue
- LinkedBlockingDeque
- ThreadPoolExecutor
- ReentrantReadWriteLock
- StampedLock
上面我只是列舉了一部分,對(duì)于 ReetrantLock 來(lái)看可以說(shuō)是并發(fā)包中非?;A(chǔ)的類,也是我們學(xué)習(xí)并發(fā)的基礎(chǔ),在后續(xù)的文章中我會(huì)給展開做更加深入的分析。
如何選擇鎖?
1.對(duì)于單機(jī)環(huán)境我們?cè)?JDK 內(nèi)進(jìn)行并發(fā)控制我們可以使用 synchronized (內(nèi)置鎖) 和 RentrantLock 。
2.對(duì)于自增或者原子數(shù)據(jù)累計(jì)我們可以使用 Unsafe 提供的原子類,比如 AtomicInteger , AtomicLong
3.對(duì)于數(shù)據(jù)庫(kù)的話,對(duì)于用戶金額扣除的場(chǎng)景我們可以使用樂(lè)觀鎖的方式來(lái)進(jìn)行控制,SQL 如下
- update table_name set amount = 100,
- version = version + 1 where id = 1 and version = 1;
4.對(duì)于分布式場(chǎng)景下我們需要保證一致性,可以使用 Redis 或者 Zk 實(shí)現(xiàn)分布式鎖。來(lái)進(jìn)行分布式場(chǎng)景下的并發(fā)控制。
參考信息
《深入理解 Java 虛擬機(jī)》周志明
https://blog.csdn.net/wangbo199308/article/details/108688109
【編輯推薦】