自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

京東二面:Java 中一共有 N 種實(shí)現(xiàn)鎖的方式,你知道都有哪些嗎?

開發(fā) 前端
樂觀讀不能保證讀到的數(shù)據(jù)是最新的,所以當(dāng)把數(shù)據(jù)讀取到局部變量的時候需要通過 lock.validate 方法來校驗(yàn)是否被修改過,如果是改過了那么就加上悲觀讀鎖,再重新讀取數(shù)據(jù)到局部變量。

首先,我們先來看下線程安全性的定義,為什么需要鎖?

線程安全,即在多線程編程中,一個程序或者代碼段在并發(fā)訪問時,能夠正確地保持其預(yù)期的行為和狀態(tài),而不會出現(xiàn)意外的錯誤或者不一致的結(jié)果。

而解決線程安全問題,主要分為兩大類:1、無鎖;2、有鎖。

無鎖的方式有:

  1. 局部變量;
  2. 對象加 final 為不可變對象;
  3. 使用 ThreadLocal 作為線程副本對象;
  4. CAS,Compare-And-Swap 即比較并交換,是 Java 十分常見的無鎖實(shí)現(xiàn)方式。

小白:那有鎖的方式呢,怎么通過加鎖保證線程安全呢?

別急哈,下面聽我給你一一道來。

Java 有哪些鎖?

從加鎖的策略看,分為隱式鎖和顯示鎖。隱式鎖通過 Synchronized 實(shí)現(xiàn),顯示鎖通過 Lock 實(shí)現(xiàn)。

  • 樂觀鎖:顧名思義,它是一種基于樂觀的思想,認(rèn)為讀取的數(shù)據(jù)一般不會沖突,不會對其加鎖,而是在最后提交數(shù)據(jù)更新時判斷數(shù)據(jù)是否被更新,如果沖突,則更新不成功。
  • 悲觀鎖:它總是假設(shè)最壞的情況,每次讀取數(shù)據(jù)都認(rèn)為別人會更新,所以每次讀取數(shù)據(jù)的時候都會加鎖,這樣別人就得阻塞等待它處理完釋放鎖后才能去讀取。

樂觀鎖實(shí)現(xiàn):CAS,比較并交換,通常指的是這樣一種原子操作:針對一個變量,首先比較它的內(nèi)存值與某個期望值是否相同,如果相同,就給它賦一個新值。

但是,這一篇我們主要來看下悲觀鎖的一些常用實(shí)現(xiàn)。

syncroized 是什么?

syncronized 是 Java 中的一個關(guān)鍵字,用于控制對共享資源的并發(fā)訪問,從而防止多個線程同時訪問某個特定資源,這被稱為同步。這個關(guān)鍵字可以用來修飾方法或代碼塊。

syncronized 使用對象鎖保證臨界區(qū)內(nèi)代碼的原子性

圖片圖片

小白:synchronized 的底層原理是什么呀,怎么自己就完成加鎖釋放鎖操作了?

其實(shí) synchronized 的原理也不難,主要有以下兩個關(guān)鍵點(diǎn)。

  • synchronized 又被稱為監(jiān)視器鎖,基于 Monitor 機(jī)制實(shí)現(xiàn)的,主要依賴底層操作系統(tǒng)的互斥原語 Mutex(互斥量)。Monitor 類比加了鎖的房間,一次只能有一個線程進(jìn)入,進(jìn)入房間即持有 Monitor,退出后就釋放 Monitor。
  • 另一個關(guān)鍵點(diǎn)是 Java 對象頭,在 JVM 虛擬機(jī)中,對象在內(nèi)存中的存儲結(jié)構(gòu)有三部分:對象頭;實(shí)例數(shù)據(jù);對齊填充。

對象頭主要包括標(biāo)記字段 Mark World,元數(shù)據(jù)指針,如果是數(shù)組對象的話,對象頭還必須存儲數(shù)組長度。

圖片圖片

synchronized 也是基于此,通過鎖對象的 monitor 獲取和 monitor 釋放來實(shí)現(xiàn),對象頭標(biāo)記為存儲具體鎖狀態(tài),ThreadId 記錄持有偏向鎖的線程 ID。

這里,又引申另外出一個問題:你知道什么是偏向鎖呢?

小白:不知道,啥玩意?

synchronized 鎖升級過程

說到這里,那就不得不提及 synchronized 的鎖升級機(jī)制了,因?yàn)?synchronized 的加鎖釋放鎖操作會使得 CPU 在內(nèi)核態(tài)和戶態(tài)之間發(fā)生切換,有一定性能開銷。在 JDK1.5 版本以后,對 synchronized 做了鎖升級的優(yōu)化,主要利用輕量級鎖、偏向鎖、自適應(yīng)鎖等減少鎖操作帶來的開銷,對其性能做了很大提升。

圖片圖片

  1. 無鎖:沒有對資源進(jìn)行加鎖
  2. 偏向鎖:在大部分情況下,只有一個線程訪問修改資源,該線程自動獲取鎖,降低了鎖操作的代價,這里就通過對象頭的 ThreadId 記錄線程 ID。
  3. 輕量級鎖:當(dāng)前持有偏向鎖,當(dāng)有另外的線程來訪問后,偏向鎖會升級為輕量級鎖,別的線程通過自旋形式嘗試獲取鎖,不會阻塞,以提高性能。
  4. 重量級鎖:在自旋次數(shù)或時間超過一定閾值時,最后會升級為重量級鎖。

小白:哦哦原來如此,那剛剛你說了 Java 除了隱式鎖之外,還有顯示鎖呢?

ReentrantLock 簡介

在 Java 中,除了對象鎖,還有顯示的加鎖的方式,比如 Lock 接口,用得比較多的就是 ReentrantLock。它的特性如下:

圖片圖片

下面我們再來對比看下 ReentrantLock 和 synchronized 的區(qū)別

圖片圖片

從這些對比就能看出 ReentrantLock 使用更加的靈活,特性更加豐富。

ReentrantLock 是一個悲觀鎖,即是同一個時刻,只允許一個線程訪問代碼塊,這一點(diǎn) synchronized 其實(shí)也一樣。

圖片圖片

小白:這個是挺好用的,但是我們有一些讀多寫少的場景中比如緩存,大部分時間都是讀操作,這里每個操作都要加鎖,讀性能不是很差嗎,有沒有更好的方案實(shí)現(xiàn)這種場景呀?

當(dāng)然有的,比如 ReentrantReadWriteLock,讀寫鎖。

ReentrantReadWriteLock 介紹

針對上述場景,Java 提供了讀寫鎖 ReentrantReadWriteLock,它的內(nèi)部維護(hù)了一對相關(guān)的鎖,一個用于只讀操作,稱為讀鎖;一個用于寫入操作,稱為寫鎖。

/** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

使用核心代碼如下:

public class LocalCacheService {

    static Map<String, Object> localCache = new HashMap<>();
    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    static Lock readL = lock.readLock();
    static Lock writeL = lock.writeLock();

    public static Object read(String key) {
        readL.lock();
        try {
            return localCache.get(key);
        } finally {
            readL.unlock();
        }
    }

    public static Object save(String key, String value) {
        writeL.lock();
        try {
            return localCache.put(key, value);
        } finally {
            writeL.unlock();
        }
    }
}

在 ReentrantReadWriteLock 中,多個線程可以同時讀取一個共享資源。

當(dāng)有其他線程的寫鎖時,讀線程會被阻塞,反之一樣。

圖片圖片

讀寫鎖設(shè)計(jì)思路

這里有一個關(guān)鍵點(diǎn),就是在 ReentrantLock 中,使用 AQS 的 state 表示同步狀態(tài),表示鎖被一個線程重復(fù)獲取的次數(shù)。但是在讀寫鎖 ReentrantReadWriteLock 中,如何用一個變量維護(hù)這兩個狀態(tài)呢?

實(shí)際 ReentrantReadWriteLock 采用“高低位切割”的方式來維護(hù),將 state 切分為兩部分:高 16 位表示讀;低 16 位表示寫。

分割之后,通過位運(yùn)算,假設(shè)當(dāng)前狀態(tài)為 S,那么:

  • 寫狀態(tài)=S&0x0000FFFF(將高 16 位全部移除),當(dāng)寫狀態(tài)需要加 1,S+1 再運(yùn)算即可。
  • 讀狀態(tài)=S>>>16(無符號補(bǔ) 0 右移 16 位),當(dāng)讀狀態(tài)需要加 1,計(jì)算 S+(1<<16)。

圖片圖片

這時,我們再來思考下,如果有線程正在讀,寫線程需要等待讀線程釋放鎖才能獲取鎖,也就是讀的時候不允許寫,那么有沒有更好的方式改進(jìn)呢?

小白:emm,這個真的難倒我了。。。。。。

什么是 StampedLock?

哈哈莫慌,Java8 已經(jīng)引入了新的讀寫鎖,StampedLock。它和 ReentrantReadWriteLock 相比,區(qū)別在于讀過程允許獲取寫鎖寫入,在原來讀寫鎖的基礎(chǔ)上加了一種樂觀鎖機(jī)制,該模式不會阻塞寫鎖,只是最后會對比原來的值,有著更高的并發(fā)性能。

StampedLock 三種模式如下:

  • 獨(dú)占鎖:和 ReentrantReadWriteLock 一樣,同一時刻只能有一個寫線程獲取資源

圖片圖片

  • 悲觀讀鎖:允許多個線程獲取讀鎖,但是讀寫互斥。

圖片圖片

  • 樂觀讀:沒有加鎖,允許多個線程獲取樂觀讀和讀鎖,同時允許一個寫線程獲取寫鎖。

圖片圖片

小白:那這里可以允許多個讀操作和也給寫線程同時進(jìn)入共享資源操作,那讀取的數(shù)據(jù)被改了怎么辦????

別擔(dān)心,樂觀讀不能保證讀到的數(shù)據(jù)是最新的,所以當(dāng)把數(shù)據(jù)讀取到局部變量的時候需要通過 lock.validate 方法來校驗(yàn)是否被修改過,如果是改過了那么就加上悲觀讀鎖,再重新讀取數(shù)據(jù)到局部變量。

責(zé)任編輯:武曉燕 來源: 碼哥跳動
相關(guān)推薦

2025-03-26 00:35:25

2024-04-19 08:05:26

鎖升級Java虛擬機(jī)

2022-04-29 13:40:55

前端測試后端

2021-08-05 07:28:25

Java實(shí)現(xiàn)方式

2025-01-21 10:04:40

Java并發(fā)阻塞隊(duì)列

2021-12-06 08:03:53

IP地址程序

2022-07-05 08:05:00

策略模式接口實(shí)現(xiàn)類

2022-08-05 08:27:05

分布式系統(tǒng)線程并發(fā)

2018-12-14 12:07:53

Nginxweb服務(wù)器

2017-06-05 18:27:41

黑科技618京東

2023-12-27 08:36:27

2024-09-29 08:21:11

2021-03-15 11:20:46

HTTPS優(yōu)化前端

2021-01-26 01:55:24

HTTPS網(wǎng)絡(luò)協(xié)議加密

2022-01-19 13:57:22

ymlSpringSnakeYml

2021-08-31 09:55:57

服務(wù)開發(fā)K8S

2022-03-18 09:42:54

JavaString

2016-02-18 16:40:29

SaaS虛擬化自動化

2021-09-10 06:50:03

內(nèi)容CDN網(wǎng)絡(luò)

2024-02-05 12:08:07

線程方式管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號