簡單了解synchronized和lock的區(qū)別
這篇文章主要介紹了簡單了解synchronized和lock的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
| 類別 | synchronized | Lock |
|---|---|---|
| 存在層次 | Java的關(guān)鍵字 | 一個類 |
| 鎖的釋放 | 1.以獲取鎖的線程執(zhí)行代碼同步代碼,釋放鎖。 2,線程執(zhí)行發(fā)生異常,jvm會讓線程釋放鎖 |
在finally中必須釋放鎖,不然容易造成線程死鎖 |
| 鎖的獲取 | 假設(shè)A線程獲得鎖,B線程等待,如果A線程阻塞,則B會一直等 | 分情況而定,Lock有多個獲取鎖的方式,線程不用一直等 |
| 鎖狀態(tài) | 無法判斷 | 可判斷 |
| 鎖類型 | 可重入,不可中斷,非公平 | 可重入,可判斷,可公平 |
| 性能 | 少量同步 | 大量同步 |
1.Lock
Lock的幾個方法
- lock() 獲取鎖
- unlock() 釋放鎖
- trylock() 獲得鎖的狀態(tài),返回true和false
- tryLock(long time,TimeUnit unit) 比tryLock加了時間期限
- lockInterruptibly()
2. synchronized
3.樂觀鎖和悲觀鎖
樂觀鎖( Optimistic Locking )
相對悲觀鎖而言,樂觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務(wù)而言,這樣的開銷往往無法承受。而樂觀鎖機(jī)制在一定程度上解決了這個問題。樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實現(xiàn)。
何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標(biāo)識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個 “version” 字段來實現(xiàn)。讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。
悲觀鎖(Pessimistic Lock)
正如其名,具有強(qiáng)烈的獨占和排他特性。它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
悲觀鎖(適合多寫場景)
總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現(xiàn)
樂觀鎖(適合多讀場景)
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號機(jī)制和CAS算法實現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于write_condition機(jī)制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現(xiàn)方式CAS實現(xiàn)的。(compare and swap)
樂觀鎖的缺點
- ABA問題
- 循環(huán)時間長開銷大
- 只能保證一個共享變量的原子操作
4.Java線程阻塞的代價
java的線程是映射到操作系統(tǒng)原生線程之上的,如果要阻塞或喚醒一個線程就需要操作系統(tǒng)介入,需要在戶態(tài)與核心態(tài)之間切換,這種切換會消耗大量的系統(tǒng)資源,因為用戶態(tài)與內(nèi)核態(tài)都有各自專用的內(nèi)存空間,專用的寄存器等,用戶態(tài)切換至內(nèi)核態(tài)需要傳遞給許多變量、參數(shù)給內(nèi)核,內(nèi)核也需要保護(hù)好用戶態(tài)在切換時的一些寄存器值、變量等,以便內(nèi)核態(tài)調(diào)用結(jié)束后切換回用戶態(tài)繼續(xù)工作。
如果線程狀態(tài)切換是一個高頻操作時,這將會消耗很多CPU處理時間;
如果對于那些需要同步的簡單的代碼塊,獲取鎖掛起操作消耗的時間比用戶代碼執(zhí)行的時間還要長,這種同步策略顯然非常糟糕的。
synchronized會導(dǎo)致爭用不到鎖的線程進(jìn)入阻塞狀態(tài),所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖,為了緩解上述性能問題,JVM從1.5開始,引入了輕量鎖與偏向鎖,默認(rèn)啟用了自旋鎖,他們都屬于樂觀鎖。
5.重量級鎖和輕量級鎖
1. 自旋鎖
自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內(nèi)釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài),它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內(nèi)核的切換的消耗。
但是線程自旋是需要消耗cup的,說白了就是讓cup在做無用功,如果一直獲取不到鎖,那線程也不能一直占用cup自旋做無用功,所以需要設(shè)定一個自旋等待的最大時間。
如果持有鎖的線程執(zhí)行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導(dǎo)致其它爭用鎖的線程在最大等待時間內(nèi)還是獲取不到鎖,這時爭用線程會停止自旋進(jìn)入阻塞狀態(tài)。
自旋鎖的優(yōu)缺點
自旋鎖盡可能的減少線程的阻塞,這對于鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小于線程阻塞掛起再喚醒的操作的消耗,這些操作會導(dǎo)致線程發(fā)生兩次上下文切換!
但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執(zhí)行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用cpu做無用功,占著XX不XX,同時有大量線程在競爭一個鎖,會導(dǎo)致獲取鎖的時間很長,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu,造成cpu的浪費。所以這種情況下我們要關(guān)閉自旋鎖;
自旋鎖時間閾值
自旋鎖的目的是為了占著CPU的資源不釋放,等到獲取到鎖立即進(jìn)行處理。但是如何去選擇自旋的執(zhí)行時間呢?如果自旋執(zhí)行時間太長,會有大量的線程處于自旋狀態(tài)占用CPU資源,進(jìn)而會影響整體系統(tǒng)的性能。因此自旋的周期選的額外重要!
JVM對于自旋周期的選擇,jdk1.5這個限度是一定的寫死的,在1.6引入了適應(yīng)性自旋鎖,適應(yīng)性自旋鎖意味著自旋的時間不在是固定的了,而是由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態(tài)來決定,基本認(rèn)為一個線程上下文切換的時間是最佳的一個時間,同時JVM還針對當(dāng)前CPU的負(fù)荷情況做了較多的優(yōu)化
- 如果平均負(fù)載小于CPUs則一直自旋
- 如果有超過(CPUs/2)個線程正在自旋,則后來線程直接阻塞
- 如果正在自旋的線程發(fā)現(xiàn)Owner發(fā)生了變化則延遲自旋時間(自旋計數(shù))或進(jìn)入阻塞
- 如果CPU處于節(jié)電模式則停止自旋
- 自旋時間的最壞情況是CPU的存儲延遲(CPU A存儲了一個數(shù)據(jù),到CPU B得知這個數(shù)據(jù)直接的時間差)
- 自旋時會適當(dāng)放棄線程優(yōu)先級之間的差異
自旋鎖的開啟
JDK1.7后,去掉此參數(shù),由jvm控制
2.重量級鎖(synchronized)
synchronized的作用
- 作用與方法時,鎖住的是對象的實例
- 當(dāng)作用于靜態(tài)方法時,鎖住的是Class實例,又因為Class的相關(guān)數(shù)據(jù)存儲在永久帶PermGen(jdk1.8則metaspace),永久帶是全局共享的,因此靜態(tài)方法鎖相當(dāng)于類的一個全局鎖,會鎖所有調(diào)用該方法的線程
- synchronized作用于一個對象實例時,鎖住的是所有以該對象為鎖的代碼塊
synchronized的實現(xiàn)
3.偏向鎖
Java偏向鎖(Biased Locking)是Java6引入的一項多線程優(yōu)化。
偏向鎖,顧名思義,它會偏向于第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發(fā)同步的,這種情況下,就會給線程加一個偏向鎖。
如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級鎖。
4.輕量級鎖
輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進(jìn)入同步塊的情況下,當(dāng)?shù)诙€線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖;
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 深入Synchronized和java.util.concurrent.locks.Lock的區(qū)別詳解
- 詳談Lock與synchronized 的區(qū)別
- Java編程synchronized與lock的區(qū)別【推薦】
- 淺談Synchronized和Lock的區(qū)別
- 通過實例解析synchronized和lock區(qū)別
- Java 多線程Synchronized和Lock的區(qū)別
- 淺談Java中Lock和Synchronized的區(qū)別
- Java常用鎖synchronized和ReentrantLock的區(qū)別
- synchronized?和?Lock?的異同點(如何讓選擇)
相關(guān)文章
Java由淺入深細(xì)數(shù)數(shù)組的操作上
數(shù)組對于每一門編程語言來說都是重要的數(shù)據(jù)結(jié)構(gòu)之一,當(dāng)然不同語言對數(shù)組的實現(xiàn)及處理也不盡相同。Java?語言中提供的數(shù)組是用來存儲固定大小的同類型元素2022-04-04
SpringBoot程序打包失敗(.jar中沒有主清單屬性)
在學(xué)習(xí)SpringBoot,打包SpringBoot程序后,在cmd運行出現(xiàn)了 某某某.jar中沒有注清單屬性,本文就來介紹一下原因以及解決方法,感興趣的可以了解一下2023-06-06
Spring實戰(zhàn)之讓Bean獲取Spring容器操作示例
這篇文章主要介紹了Spring實戰(zhàn)之讓Bean獲取Spring容器操作,結(jié)合實例形式分析了Bean獲取Spring容器的相關(guān)原理、實現(xiàn)方法及操作注意事項,需要的朋友可以參考下2019-11-11
java編程中拷貝數(shù)組的方式及相關(guān)問題分析
這篇文章主要介紹了java編程中拷貝數(shù)組的方式及相關(guān)問題分析,分享了Java中數(shù)組復(fù)制的四種方式,其次對二維數(shù)組的簡單使用有一段代碼示例,具有一定參考價值,需要的朋友可以了解下。2017-11-11
一文搞懂JMeter engine中HashTree的配置問題
本文主要介紹了JMeter engine中HashTree的配置,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09

