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

簡直了,被“Java并發(fā)鎖”問題追問到自閉

開發(fā) 前端
本文咱們就來聊聊這些Java并發(fā)鎖的理解吧。我們將從以下這些方面來一起回顧一下Java中的并發(fā)鎖。

故事

地鐵上,小帥雙目空洞地望著窗外...絕望,發(fā)自內(nèi)心地感到絕望...

距離失業(yè)已經(jīng)過去兩個(gè)月了,這是小帥接到的第四次面試邀請?!盎厝サ韧ㄖ?..”,簡簡單單的六個(gè)字,把小帥的心再次打入了冰窖。

上次“【ThreadLocal問出花】”,小帥其實(shí)也有吸取教訓(xùn)得,這次對(duì)于多線程的問題還是做了很多準(zhǔn)備的...可是沒想到這次的結(jié)果居然也還是這樣。

“Java中的鎖了解吧?介紹一下吧”,面試官不緊不慢地問到。

“樂觀鎖、悲觀鎖、公平鎖、非公平鎖,然后平時(shí)咱們的synchronized是基于.....”小帥把知道的所有關(guān)于鎖的基本都回答了一遍。

面試官對(duì)他笑了笑,“就這些嗎?還有呢?比如自旋鎖、可重入鎖、獨(dú)占鎖....并且說一下你的理解,或者聊一下使用場景的優(yōu)劣吧?!?/p>

“額.....以前好像看到過...”小帥語無倫次地回答到。

“嗯,行吧,之前的那些答得可以的,不過一會(huì)我這邊有個(gè)會(huì),要不今天咱們就聊到這里?回去等通知吧...”

Java中讓人眼花繚亂的鎖你是否真的一一清楚了?

試問這樣一個(gè)大而寬的問題,大家能夠總結(jié)全嗎,如果讓各位來回答,能否回答完全呢?

我們在實(shí)際的并發(fā)編程中,常常遇到多個(gè)線程訪問一個(gè)共享變量的情況,當(dāng)同時(shí)對(duì)共享變量進(jìn)行讀寫操作的時(shí)候,就會(huì)產(chǎn)生數(shù)據(jù)不一致的情況。為了保證資源獲取的有序性,我們就常常會(huì)用到并發(fā)鎖。

那么接下來咱們就來聊聊這些Java并發(fā)鎖的理解吧。我們將從以下這些方面來一起回顧一下Java中的并發(fā)鎖。

概要

樂觀鎖和悲觀鎖:線程是否鎖住同步資源

大家其實(shí)對(duì)樂觀鎖和悲觀鎖聽說的比較多一些,所以咱們就先來聊聊這兩種類型的鎖。這兩種類型的鎖,本質(zhì)區(qū)分是要看線程是否鎖住同步資源。

先來看一下悲觀鎖。悲觀鎖就是每次去拿數(shù)據(jù)的時(shí)候都會(huì)認(rèn)為別人會(huì)修改數(shù)據(jù),所以在讀取數(shù)據(jù)的時(shí)候都會(huì)上鎖。這樣就會(huì)導(dǎo)致線程臨時(shí)阻塞。

悲觀鎖

再來看一下樂觀鎖,樂觀鎖就是每次在拿數(shù)據(jù)的時(shí)候都假設(shè)別人不會(huì)修改數(shù)據(jù),所以都不會(huì)進(jìn)行上鎖;只有在更新數(shù)據(jù)的時(shí)候才去判斷之前有沒有別的線程更新了這條數(shù)據(jù)。如果沒有更新,那么當(dāng)前線程會(huì)自己修改數(shù)據(jù)并且寫入成功。如果數(shù)據(jù)已經(jīng)被其他線程更新了,那么會(huì)報(bào)錯(cuò)或者自動(dòng)重試,例如下圖。

樂觀鎖

上述兩種鎖,并沒有優(yōu)劣之分。只是看相關(guān)的場景然后分別去使用。

  • 樂觀鎖:適用于寫少讀多的場景。因?yàn)椴挥蒙湘i,釋放鎖,省去了鎖的開銷,從而提升了吞吐量。
  • 悲觀鎖:適用于寫多讀少的場景。因?yàn)榫€程競爭激烈,如果使用樂觀鎖會(huì)導(dǎo)致線程不斷進(jìn)行重試,反而降低吞吐量。

共享鎖和獨(dú)占鎖:多個(gè)線程是否共享同一把鎖

并發(fā)場景下,如果多個(gè)線程能夠共享一把鎖,那么就是所謂的共享鎖,如果不能,那么則為獨(dú)占鎖(其他命名:排他鎖或者獨(dú)享鎖)。

共享鎖指鎖可以被多個(gè)線程持有。如果一個(gè)線程對(duì)數(shù)據(jù)加上共享鎖,那么其他線程只能對(duì)數(shù)據(jù)再加共享鎖,不能加獨(dú)占鎖。另外的共享鎖的線程只能讀數(shù)據(jù),不能修改數(shù)據(jù)。如下圖。

共享鎖

獨(dú)占鎖是指鎖一次只能被一個(gè)線程持有,如果一個(gè)線程對(duì)數(shù)據(jù)加上獨(dú)占鎖,那么其他的線程則不能對(duì)該數(shù)據(jù)再加任何類型的鎖。如果一個(gè)線程獲取獨(dú)占鎖,那么則該線程既可以讀數(shù)據(jù)又可以修改數(shù)據(jù)。

獨(dú)占鎖

對(duì)于獨(dú)占鎖來說,大家比較熟悉的就是synchronized和J.U.C包中的Lock實(shí)現(xiàn)類。

大家可能也聽說過互斥鎖,其實(shí)互斥鎖就是獨(dú)占鎖的一種常規(guī)實(shí)現(xiàn)。

讀寫鎖是共享鎖的一種具體實(shí)現(xiàn)。讀寫鎖管理一組鎖,一個(gè)是只讀的鎖,一個(gè)是寫鎖。

讀鎖可以再?zèng)]有寫鎖的時(shí)候被多個(gè)線程同時(shí)持有,而寫鎖是獨(dú)占的,于此同時(shí)寫鎖的優(yōu)先級(jí)要高于讀鎖,一個(gè)獲得了讀鎖的線程必須能看到前一個(gè)釋放的寫鎖更新的內(nèi)容。

讀寫鎖和互斥鎖對(duì)比,其性能更高,每次只有一個(gè)寫線程,但是有多個(gè)線程可以并發(fā)讀。

讀寫鎖

例如,ReentrantReadWriteLock。具體偽代碼如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 公眾號(hào):程序員老貓
 **/
public class ReadWriteLockDemo {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void readData() {
        lock.readLock().lock(); // 獲取讀鎖
        try {
            // 讀取共享數(shù)據(jù)
        } finally {
            lock.readLock().unlock(); // 釋放讀鎖
        }
    }

    public void writeData() {
        lock.writeLock().lock(); // 獲取寫鎖
        try {
           // 修改或?qū)懭霐?shù)據(jù)
        } finally {
            lock.writeLock().unlock(); // 釋放寫鎖
        }
    }
}

公平鎖和非公平鎖:多線程競爭時(shí)是否要排隊(duì)

我們根據(jù)多線程在競爭鎖的時(shí)候是否需要排隊(duì)從來判斷其鎖的類型是公平鎖還是非公平鎖。

公平鎖指多個(gè)線程按照申請鎖的順序來獲取鎖。類似食堂排隊(duì)打飯,先到的可以先打飯。

公平鎖

非公平鎖是指多個(gè)線程獲取鎖的順序并不是按照申請鎖的順序進(jìn)行的,有可能后申請的比先申請的優(yōu)先獲得鎖,高并發(fā)場景下,優(yōu)先級(jí)就有可能發(fā)生反轉(zhuǎn)。如下圖:

咱們在日常開發(fā)的過程中經(jīng)常用到synchronized,其底層其實(shí)就是非公平鎖。當(dāng)然如果我們要使用公平鎖的情況下,我們也可以使用ReentrantLock。偽代碼如下:

Lock lock = new ReetrantLock(false);

ReentrantLock默認(rèn)為非公平鎖,設(shè)置為true的時(shí)候表示公平鎖。當(dāng)設(shè)置為false的時(shí)候表示非公平鎖。

可重入鎖和不可重入鎖:同一個(gè)線程中多個(gè)流程是否能夠獲取同一把鎖。

如果一個(gè)線程中的多個(gè)流程能夠獲取同一把鎖,那么我們就叫該所為可重入鎖,反之則為不可重入鎖。咱們光看文字描述的話可能比較抽象。我們看一下下圖。

在Java中可重入鎖一般有ReentrantLock,其命名就已經(jīng)很明確了。另外的synchronized也是可重入鎖??芍厝腈i的優(yōu)勢是可以一定程度上避免死鎖發(fā)生。上面的示意圖轉(zhuǎn)換為如下demo:

public synchronized void methodA() {
  methodB()
}

public synchronized void methodB() {
  methodC()
}

public synchronized void methodC(){
  doSomeThing()
}

自旋鎖或者自適應(yīng)自旋鎖:線程鎖定同步資源失敗,如該線程沒有被阻塞場景下發(fā)生

如果一個(gè)線程鎖住同步資源失敗,但是又希望這個(gè)線程不被阻塞,那么此時(shí)咱們就可以使用自旋鎖或者自適應(yīng)自旋鎖。自旋鎖指線程沒有獲得鎖的情況下不被掛起,而是執(zhí)行一個(gè)忙循環(huán)。那么這個(gè)忙循環(huán)的話就成為自旋。如下:

自旋鎖

目的:減少線程被掛起的概率,因?yàn)榫€程被掛起和喚醒也是消費(fèi)資源。

Java中AtomicInteger類就有自旋的操作,如下源代碼:

@HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

上述方法中weakCompareAndSetInt(),就可以被稱為是CAS操作,如果失敗,那么會(huì)一直循環(huán)獲取當(dāng)前的value值然后進(jìn)行重試操作。那么這個(gè)過程其實(shí)就是自旋了。

其他分類的鎖

上述我們聊到的這系列的鎖應(yīng)該是大家聽到比較多的。其實(shí)還有其他的分類。在此不做一一展開了,有興趣的小伙伴當(dāng)然也可以深入去了解一下。例如根據(jù)線程競爭同步資源的時(shí)候,細(xì)節(jié)流程是否發(fā)生變化,分為偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。在比如,相信大家對(duì)HashMap底層原理倒背如流吧,對(duì)ConcurrentHashMap應(yīng)該也有了解,那么ConcurrentHashMap底層其實(shí)將鎖的粒度進(jìn)一步細(xì)化了,存在了分段鎖的概念等等。

總結(jié)

這些讓人眼花繚亂的鎖,如果面試官問到的話,大家是否能夠說出一二呢?相信看完上面的解釋,大家心里多多少少也有數(shù)了吧。當(dāng)然關(guān)于最后一點(diǎn)其他分類的鎖,老貓沒有展開。有興趣的小伙伴可以自行查閱一下這些分類。

責(zé)任編輯:趙寧寧 來源: 程序員老貓
相關(guān)推薦

2021-09-08 05:43:28

網(wǎng)盤硬盤軟件

2019-01-23 16:20:30

Python火車票程序員

2019-11-29 10:16:36

高并發(fā)系統(tǒng)緩存

2021-04-23 10:25:40

程序員12306搶票

2023-04-10 09:32:00

DubboJava

2021-09-13 08:41:52

職場互聯(lián)網(wǎng)自閉

2023-10-26 16:02:04

線程

2025-03-03 04:00:00

線程安全CPU

2020-07-06 08:03:32

Java悲觀鎖樂觀鎖

2020-07-07 07:47:07

Java無鎖技術(shù)

2021-03-10 08:47:46

反射Java對(duì)象

2017-12-18 17:21:56

AndroidJava內(nèi)存泄漏

2024-03-06 08:00:56

javaAQS原生

2021-01-15 05:12:14

Java并發(fā)樂觀鎖

2012-08-03 09:14:23

2024-09-29 08:39:51

2022-12-13 18:09:25

連接狀態(tài)客戶端

2022-08-24 10:45:12

RustC++并發(fā)特性

2024-02-29 09:44:36

Java工具

2009-03-02 10:13:00

騰訊馬化騰創(chuàng)業(yè)經(jīng)驗(yàn)
點(diǎn)贊
收藏

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