溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java 中的鎖是什么意思

發(fā)布時(shí)間:2021-07-12 11:49:51 來(lái)源:億速云 閱讀:166 作者:chen 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Java 中的鎖是什么意思”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Java 中的鎖是什么意思”吧!

鎖的類(lèi)型

鎖的類(lèi)型從不同的角度看,主要分為以下幾種

  • 悲觀(guān)鎖、樂(lè)觀(guān)鎖

  • 阻塞、非阻塞、自旋鎖

  • 公平、非公平

  • 可重入、不可重入

  • 共享鎖、排他鎖

悲觀(guān)鎖和樂(lè)觀(guān)鎖

悲觀(guān)鎖

  • 悲觀(guān)鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定有別的線(xiàn)程來(lái)修改數(shù)據(jù),因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖,確保數(shù)據(jù)不會(huì)被別的線(xiàn)程修改。

  • 悲觀(guān)鎖適合寫(xiě)操作多的場(chǎng)景,先加鎖可以保證寫(xiě)操作時(shí)數(shù)據(jù)正確。

樂(lè)觀(guān)鎖

  • 樂(lè)觀(guān)鎖認(rèn)為自己在使用數(shù)據(jù)時(shí)不會(huì)有別的線(xiàn)程修改數(shù)據(jù),所以不會(huì)添加鎖,只是在更新數(shù)據(jù)的時(shí)候去判斷之前有沒(méi)有別的線(xiàn)程更新了這個(gè)數(shù)據(jù)。如果這個(gè)數(shù)據(jù)沒(méi)有被更新,當(dāng)前線(xiàn)程將自己修改的數(shù)據(jù)成功寫(xiě)入。

  • 樂(lè)觀(guān)鎖適合讀操作多的場(chǎng)景,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。

  • 樂(lè)觀(guān)鎖在java中通過(guò)使用無(wú)鎖編程來(lái)實(shí)現(xiàn),原子類(lèi)中的遞增操作就是通過(guò)CAS自旋來(lái)實(shí)現(xiàn)的。偽代碼如下:

do {
    value = getXXXVolatile()
} while(!compareAndSwapInt(obj, address, value, value + delta))

CAS

CAS全稱(chēng) Compare And Swap(比較與交換),是一種無(wú)鎖算法。在不使用鎖(沒(méi)有線(xiàn)程被阻塞)的情況下實(shí)現(xiàn)多線(xiàn)程之間的變量同步。

CAS是一個(gè)原子操作,底層通過(guò)Unsafe類(lèi)進(jìn)行操作。

阻塞、非阻塞、自旋鎖

阻塞或喚醒一個(gè)Java線(xiàn)程需要操作系統(tǒng)切換CPU狀態(tài)來(lái)完成,這種狀態(tài)轉(zhuǎn)換需要耗費(fèi)處理器時(shí)間。如果同步代碼塊中的內(nèi)容過(guò)于簡(jiǎn)單,狀態(tài)轉(zhuǎn)換消耗的時(shí)間有可能比用戶(hù)代碼執(zhí)行的時(shí)間還要長(zhǎng)。

在許多場(chǎng)景中,同步資源的鎖定時(shí)間很短,為了這一小段時(shí)間去切換線(xiàn)程,線(xiàn)程掛起和恢復(fù)現(xiàn)場(chǎng)的花費(fèi)可能會(huì)讓系統(tǒng)得不償失。如果物理機(jī)器有多個(gè)處理器,能夠讓兩個(gè)或以上的線(xiàn)程同時(shí)并行執(zhí)行,我們就可以讓后面那個(gè)請(qǐng)求鎖的線(xiàn)程不放棄CPU的執(zhí)行時(shí)間,看看持有鎖的線(xiàn)程是否很快就會(huì)釋放鎖。

而為了讓當(dāng)前線(xiàn)程“稍等一下”,我們需讓當(dāng)前線(xiàn)程進(jìn)行自旋,如果在自旋完成后前面鎖定同步資源的線(xiàn)程已經(jīng)釋放了鎖,那么當(dāng)前線(xiàn)程就可以不必阻塞而是直接獲取同步資源,從而避免切換線(xiàn)程的開(kāi)銷(xiāo)。這就是自旋鎖。

自旋鎖本身是有缺點(diǎn)的,它不能代替阻塞。自旋等待雖然避免了線(xiàn)程切換的開(kāi)銷(xiāo),但它要占用處理器時(shí)間。如果鎖被占用的時(shí)間很短,自旋等待的效果就會(huì)非常好。反之,如果鎖被占用的時(shí)間很長(zhǎng),那么自旋的線(xiàn)程只會(huì)白浪費(fèi)處理器資源。所以,自旋等待的時(shí)間必須要有一定的限度,如果自旋超過(guò)了限定次數(shù)(默認(rèn)是10次,可以使用-XX:PreBlockSpin來(lái)更改)沒(méi)有成功獲得鎖,就應(yīng)當(dāng)掛起線(xiàn)程。

自旋鎖的實(shí)現(xiàn)原理同樣也是CAS

公平鎖和非公平鎖

公平鎖是指多個(gè)線(xiàn)程按申請(qǐng)鎖的順序來(lái)獲取鎖,線(xiàn)程直接進(jìn)入隊(duì)列中排隊(duì),隊(duì)列中的第一個(gè)線(xiàn)程才能獲得鎖。

公平鎖的優(yōu)點(diǎn)是等待鎖的線(xiàn)程不會(huì)餓死。缺點(diǎn)是整體吞吐效率相對(duì)非公平鎖要低,等待隊(duì)列中除第一個(gè)線(xiàn)程以外的所有線(xiàn)程都會(huì)阻塞,CPU喚醒阻塞線(xiàn)程的開(kāi)銷(xiāo)比非公平鎖大。

非公平鎖是多個(gè)線(xiàn)程加鎖時(shí)直接嘗試獲取鎖,獲取不到才會(huì)到等待隊(duì)列的隊(duì)尾等待。但如果此時(shí)鎖剛好可用,那么這個(gè)線(xiàn)程可以無(wú)需阻塞直接獲取到鎖,所以非公平鎖有可能出現(xiàn)后申請(qǐng)鎖的線(xiàn)程先獲取鎖的場(chǎng)景。非公平鎖的優(yōu)點(diǎn)是可以減少喚起線(xiàn)程的開(kāi)銷(xiāo),整體的吞吐效率高,因?yàn)榫€(xiàn)程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線(xiàn)程。缺點(diǎn)是處于等待隊(duì)列中的線(xiàn)程可能會(huì)餓死,或者等很久才會(huì)獲得鎖。

ReentrantLock支持公平鎖和非公平鎖兩種,默認(rèn)是非公平鎖。

公平和非公平鎖內(nèi)部都依賴(lài)AQS來(lái)實(shí)現(xiàn)。

AQS

AQS全程AbstractQueuedSynchronizer,即抽象同步隊(duì)列。內(nèi)部主要包含以下重要組件:

  1. CLH 等待隊(duì)列,雙向鏈表,每個(gè)節(jié)點(diǎn)包含了Thread,nextWaiter和waitState,前驅(qū)節(jié)點(diǎn)和后驅(qū)節(jié)點(diǎn)

  2. state 同步狀態(tài),初始值為0.

  3. 條件變量

公平和非公平的實(shí)現(xiàn)是放在AQS的兩個(gè)子類(lèi)中實(shí)現(xiàn)的。

  • 公平鎖就是通過(guò)同步隊(duì)列來(lái)實(shí)現(xiàn)多個(gè)線(xiàn)程按照申請(qǐng)鎖的順序來(lái)獲取鎖,從而實(shí)現(xiàn)公平的特性。公平鎖在嘗試加鎖的時(shí)候,會(huì)檢測(cè)當(dāng)前線(xiàn)程是否是CLH等待隊(duì)列中的第一個(gè)。

  • 非公平鎖加鎖時(shí)不考慮排隊(duì)等待問(wèn)題,直接嘗試獲取鎖,所以存在后申請(qǐng)卻先獲得鎖的情況。

可重入鎖和非可重入鎖

可重入鎖又名遞歸鎖,是指在同一個(gè)線(xiàn)程在外層方法獲取鎖的時(shí)候,再進(jìn)入該線(xiàn)程的內(nèi)層方法會(huì)自動(dòng)獲取鎖(前提鎖對(duì)象得是同一個(gè)對(duì)象或者class),不會(huì)因?yàn)橹耙呀?jīng)獲取過(guò)還沒(méi)釋放而阻塞。Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖。

而非可重入鎖則限定一個(gè)線(xiàn)程內(nèi)的同一個(gè)鎖,只能被一個(gè)資源獲取。

AQS邏輯

當(dāng)線(xiàn)程嘗試獲取鎖時(shí),可重入鎖先嘗試獲取并更新status值,如果status == 0表示沒(méi)有其他線(xiàn)程在執(zhí)行同步代碼,則把status置為1,當(dāng)前線(xiàn)程開(kāi)始執(zhí)行。如果status != 0,則判斷當(dāng)前線(xiàn)程是否是獲取到這個(gè)鎖的線(xiàn)程,如果是的話(huà)執(zhí)行status+1,且當(dāng)前線(xiàn)程可以再次獲取鎖。而非可重入鎖是直接去獲取并嘗試更新當(dāng)前status的值,如果status != 0的話(huà)會(huì)導(dǎo)致其獲取鎖失敗,當(dāng)前線(xiàn)程阻塞。

排他鎖和共享鎖

共享鎖和排他鎖也稱(chēng)讀寫(xiě)鎖,分為讀鎖和寫(xiě)鎖。

  • 讀鎖為共享鎖,指該鎖可以被多個(gè)線(xiàn)程所持有,如果線(xiàn)程T對(duì)數(shù)據(jù)A加上共享鎖后,則其他線(xiàn)程只能對(duì)A再加共享鎖,不能加排它鎖。獲得共享鎖的線(xiàn)程只能讀數(shù)據(jù),不能修改數(shù)據(jù)。

  • 寫(xiě)鎖為排他鎖,指該鎖一次只能被一個(gè)線(xiàn)程所持有。如果線(xiàn)程T對(duì)數(shù)據(jù)A加上排它鎖后,則其他線(xiàn)程不能再對(duì)A加任何類(lèi)型的鎖。獲得排它鎖的線(xiàn)程即能讀數(shù)據(jù)又能修改數(shù)據(jù)。

也是通過(guò)AQS來(lái)實(shí)現(xiàn)的,通過(guò)實(shí)現(xiàn)不同的方法,來(lái)實(shí)現(xiàn)獨(dú)享或者共享。

AQS實(shí)現(xiàn)

在jdk中讀寫(xiě)鎖的實(shí)現(xiàn)類(lèi)為ReentrantReadWriteLock,它內(nèi)部有兩個(gè)鎖:ReadLock和WriteLock。讀鎖和寫(xiě)鎖的鎖主體都是Sync,但讀鎖和寫(xiě)鎖的加鎖方式不一樣。讀鎖是共享鎖,寫(xiě)鎖是獨(dú)享鎖。讀鎖的共享鎖可保證并發(fā)讀非常高效,而讀寫(xiě)、寫(xiě)讀、寫(xiě)寫(xiě)的過(guò)程互斥,因?yàn)樽x鎖和寫(xiě)鎖是分離的。所以ReentrantReadWriteLock的并發(fā)性相比一般的互斥鎖有了很大提升。

在ReentrantReadWriteLock中,將state字段拆成了兩部分:高16位和低16位。高16位表示讀狀態(tài),低16位表示寫(xiě)狀態(tài)。

如果存在讀鎖,則寫(xiě)鎖不能被獲取,原因在于:必須確保寫(xiě)鎖的操作對(duì)讀鎖可見(jiàn),如果允許讀鎖在已被獲取的情況下對(duì)寫(xiě)鎖的獲取,那么正在運(yùn)行的其他讀線(xiàn)程就無(wú)法感知到當(dāng)前寫(xiě)線(xiàn)程的操作。

因此,只有等待其他讀線(xiàn)程都釋放了讀鎖,寫(xiě)鎖才能被當(dāng)前線(xiàn)程獲取,而寫(xiě)鎖一旦被獲取,則其他讀寫(xiě)線(xiàn)程的后續(xù)訪(fǎng)問(wèn)均被阻塞。寫(xiě)鎖的釋放與ReentrantLock的釋放過(guò)程基本類(lèi)似,每次釋放均減少寫(xiě)狀態(tài),當(dāng)寫(xiě)狀態(tài)為0時(shí)表示寫(xiě)鎖已被釋放,然后等待的讀寫(xiě)線(xiàn)程才能夠繼續(xù)訪(fǎng)問(wèn)讀寫(xiě)鎖,同時(shí)前次寫(xiě)線(xiàn)程的修改對(duì)后續(xù)的讀寫(xiě)線(xiàn)程可見(jiàn)。

到此,相信大家對(duì)“Java 中的鎖是什么意思”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guā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)容。

AI