溫馨提示×

溫馨提示×

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

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

簡單了解synchronized和lock的區(qū)別

發(fā)布時(shí)間:2020-09-30 02:28:07 來源:腳本之家 閱讀:122 作者:wellwill 欄目:編程語言

這篇文章主要介紹了簡單了解synchronized和lock的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

類別 synchronized Lock
存在層次 Java的關(guān)鍵字 一個(gè)類
鎖的釋放 1.以獲取鎖的線程執(zhí)行代碼同步代碼,釋放鎖。
2,線程執(zhí)行發(fā)生異常,jvm會(huì)讓線程釋放鎖
在finally中必須釋放鎖,不然容易造成線程死鎖
鎖的獲取 假設(shè)A線程獲得鎖,B線程等待,如果A線程阻塞,則B會(huì)一直等 分情況而定,Lock有多個(gè)獲取鎖的方式,線程不用一直等
鎖狀態(tài) 無法判斷 可判斷
鎖類型 可重入,不可中斷,非公平 可重入,可判斷,可公平
性能 少量同步 大量同步

1.Lock

Lock的幾個(gè)方法

  • lock() 獲取鎖
  • unlock() 釋放鎖
  • trylock() 獲得鎖的狀態(tài),返回true和false
  • tryLock(long time,TimeUnit unit) 比tryLock加了時(shí)間期限
  • lockInterruptibly()

2. synchronized

3.樂觀鎖和悲觀鎖

樂觀鎖( Optimistic Locking )

相對(duì)悲觀鎖而言,樂觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對(duì)長事務(wù)而言,這樣的開銷往往無法承受。而樂觀鎖機(jī)制在一定程度上解決了這個(gè)問題。樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。

何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個(gè) “version” 字段來實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。

悲觀鎖(Pessimistic Lock)

正如其名,具有強(qiáng)烈的獨(dú)占和排他特性。它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。

悲觀鎖(適合多寫場景)

總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖(共享資源每次只給一個(gè)線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)

樂觀鎖(適合多讀場景)

總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)機(jī)制和CAS算法實(shí)現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的。(compare and swap)

樂觀鎖的缺點(diǎn)

  • ABA問題
  • 循環(huán)時(shí)間長開銷大
  • 只能保證一個(gè)共享變量的原子操作

4.Java線程阻塞的代價(jià)

java的線程是映射到操作系統(tǒng)原生線程之上的,如果要阻塞或喚醒一個(gè)線程就需要操作系統(tǒng)介入,需要在戶態(tài)與核心態(tài)之間切換,這種切換會(huì)消耗大量的系統(tǒng)資源,因?yàn)橛脩魬B(tài)與內(nèi)核態(tài)都有各自專用的內(nèi)存空間,專用的寄存器等,用戶態(tài)切換至內(nèi)核態(tài)需要傳遞給許多變量、參數(shù)給內(nèi)核,內(nèi)核也需要保護(hù)好用戶態(tài)在切換時(shí)的一些寄存器值、變量等,以便內(nèi)核態(tài)調(diào)用結(jié)束后切換回用戶態(tài)繼續(xù)工作。

如果線程狀態(tài)切換是一個(gè)高頻操作時(shí),這將會(huì)消耗很多CPU處理時(shí)間;

如果對(duì)于那些需要同步的簡單的代碼塊,獲取鎖掛起操作消耗的時(shí)間比用戶代碼執(zhí)行的時(shí)間還要長,這種同步策略顯然非常糟糕的。

synchronized會(huì)導(dǎo)致爭用不到鎖的線程進(jìn)入阻塞狀態(tài),所以說它是java語言中一個(gè)重量級(jí)的同步操縱,被稱為重量級(jí)鎖,為了緩解上述性能問題,JVM從1.5開始,引入了輕量鎖與偏向鎖,默認(rèn)啟用了自旋鎖,他們都屬于樂觀鎖。

5.重量級(jí)鎖和輕量級(jí)鎖

1. 自旋鎖

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài),它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內(nèi)核的切換的消耗。

但是線程自旋是需要消耗cup的,說白了就是讓cup在做無用功,如果一直獲取不到鎖,那線程也不能一直占用cup自旋做無用功,所以需要設(shè)定一個(gè)自旋等待的最大時(shí)間。

如果持有鎖的線程執(zhí)行的時(shí)間超過自旋等待的最大時(shí)間扔沒有釋放鎖,就會(huì)導(dǎo)致其它爭用鎖的線程在最大等待時(shí)間內(nèi)還是獲取不到鎖,這時(shí)爭用線程會(huì)停止自旋進(jìn)入阻塞狀態(tài)。

自旋鎖的優(yōu)缺點(diǎn)

自旋鎖盡可能的減少線程的阻塞,這對(duì)于鎖的競爭不激烈,且占用鎖時(shí)間非常短的代碼塊來說性能能大幅度的提升,因?yàn)樽孕南臅?huì)小于線程阻塞掛起再喚醒的操作的消耗,這些操作會(huì)導(dǎo)致線程發(fā)生兩次上下文切換!

但是如果鎖的競爭激烈,或者持有鎖的線程需要長時(shí)間占用鎖執(zhí)行同步塊,這時(shí)候就不適合使用自旋鎖了,因?yàn)樽孕i在獲取鎖前一直都是占用cpu做無用功,占著XX不XX,同時(shí)有大量線程在競爭一個(gè)鎖,會(huì)導(dǎo)致獲取鎖的時(shí)間很長,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu,造成cpu的浪費(fèi)。所以這種情況下我們要關(guān)閉自旋鎖;

自旋鎖時(shí)間閾值

自旋鎖的目的是為了占著CPU的資源不釋放,等到獲取到鎖立即進(jìn)行處理。但是如何去選擇自旋的執(zhí)行時(shí)間呢?如果自旋執(zhí)行時(shí)間太長,會(huì)有大量的線程處于自旋狀態(tài)占用CPU資源,進(jìn)而會(huì)影響整體系統(tǒng)的性能。因此自旋的周期選的額外重要!

JVM對(duì)于自旋周期的選擇,jdk1.5這個(gè)限度是一定的寫死的,在1.6引入了適應(yīng)性自旋鎖,適應(yīng)性自旋鎖意味著自旋的時(shí)間不在是固定的了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間以及鎖的擁有者的狀態(tài)來決定,基本認(rèn)為一個(gè)線程上下文切換的時(shí)間是最佳的一個(gè)時(shí)間,同時(shí)JVM還針對(duì)當(dāng)前CPU的負(fù)荷情況做了較多的優(yōu)化

  • 如果平均負(fù)載小于CPUs則一直自旋
  • 如果有超過(CPUs/2)個(gè)線程正在自旋,則后來線程直接阻塞
  • 如果正在自旋的線程發(fā)現(xiàn)Owner發(fā)生了變化則延遲自旋時(shí)間(自旋計(jì)數(shù))或進(jìn)入阻塞
  • 如果CPU處于節(jié)電模式則停止自旋
  • 自旋時(shí)間的最壞情況是CPU的存儲(chǔ)延遲(CPU A存儲(chǔ)了一個(gè)數(shù)據(jù),到CPU B得知這個(gè)數(shù)據(jù)直接的時(shí)間差)
  • 自旋時(shí)會(huì)適當(dāng)放棄線程優(yōu)先級(jí)之間的差異

自旋鎖的開啟

JDK1.7后,去掉此參數(shù),由jvm控制

2.重量級(jí)鎖(synchronized)

synchronized的作用

  • 作用與方法時(shí),鎖住的是對(duì)象的實(shí)例
  • 當(dāng)作用于靜態(tài)方法時(shí),鎖住的是Class實(shí)例,又因?yàn)镃lass的相關(guān)數(shù)據(jù)存儲(chǔ)在永久帶PermGen(jdk1.8則metaspace),永久帶是全局共享的,因此靜態(tài)方法鎖相當(dāng)于類的一個(gè)全局鎖,會(huì)鎖所有調(diào)用該方法的線程
  • synchronized作用于一個(gè)對(duì)象實(shí)例時(shí),鎖住的是所有以該對(duì)象為鎖的代碼塊

synchronized的實(shí)現(xiàn)

3.偏向鎖

Java偏向鎖(Biased Locking)是Java6引入的一項(xiàng)多線程優(yōu)化。

偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問鎖的線程,如果在運(yùn)行過程中,同步鎖只有一個(gè)線程訪問,不存在多線程爭用的情況,則線程是不需要觸發(fā)同步的,這種情況下,就會(huì)給線程加一個(gè)偏向鎖。

如果在運(yùn)行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會(huì)被掛起,JVM會(huì)消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級(jí)鎖。

4.輕量級(jí)鎖

輕量級(jí)鎖是由偏向所升級(jí)來的,偏向鎖運(yùn)行在一個(gè)線程進(jìn)入同步塊的情況下,當(dāng)?shù)诙€(gè)線程加入鎖爭用的時(shí)候,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖;

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

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

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI