【分布式鎖的演化】什么是鎖情萤?

從本篇開(kāi)始鸭蛙,我們來(lái)好好梳理一下Java開(kāi)發(fā)中的鎖,通過(guò)一些具體簡(jiǎn)單的例子來(lái)描述清楚從Java單體鎖到分布式鎖的演化流程筋岛。本篇我們先來(lái)看看什么是鎖娶视,以下老貓會(huì)通過(guò)一些日常生活中的例子也說(shuō)清楚鎖的概念。

描述

鎖在Java中是一個(gè)非常重要的概念泉蝌,在當(dāng)今的互聯(lián)網(wǎng)時(shí)代歇万,尤其在各種高并發(fā)的情況下揩晴,我們更加離不開(kāi)鎖勋陪。那么到底什么是鎖呢?在計(jì)算機(jī)中硫兰,鎖(lock)或者互斥(mutex)是一種同步機(jī)制诅愚,用于在有許多執(zhí)行線程的環(huán)境中強(qiáng)制對(duì)資源的訪問(wèn)限制。鎖可以強(qiáng)制實(shí)施排他互斥劫映、并發(fā)控制策略违孝。舉一個(gè)生活中的例子,大家都去超市買東西泳赋,如果我們帶了包的話雌桑,要放到儲(chǔ)物柜。我們?cè)侔堰@個(gè)例子極端一下祖今,假如柜子只有一個(gè)校坑,那么此時(shí)同時(shí)來(lái)了三個(gè)人A、B千诬、C都要往這個(gè)柜子里放東西耍目。那么這個(gè)場(chǎng)景就是一個(gè)多線程,多線程自然也就離不開(kāi)鎖徐绑。簡(jiǎn)單示意圖如下


存儲(chǔ)柜子模型

A邪驮、B、C都要往柜子里面放東西傲茄,可是柜子只能存放一個(gè)東西毅访,那么怎么處理?這個(gè)時(shí)候我們就引出了鎖的概念盘榨,三個(gè)人中誰(shuí)先搶到了柜子的鎖俺抽,誰(shuí)就可以使用這個(gè)柜子,其他的人只能等待较曼。比如C搶到了鎖磷斧,C就可以使用這個(gè)柜子,A和B只能等待,等到C使用完畢之后弛饭,釋放了鎖冕末,AB再進(jìn)行搶鎖,誰(shuí)先搶到了侣颂,誰(shuí)就有使用柜子的權(quán)利档桃。

抽象成代碼

我們其實(shí)可以將以上場(chǎng)景抽象程相關(guān)的代碼模型,我們來(lái)看一下以下代碼的例子憔晒。

/**
 * @author kdaddy@163.com
 * @date 2020/11/2 23:13
 */
public class Cabinet {
    //表示柜子中存放的數(shù)字
    private int storeNumber;

    public int getStoreNumber() {
        return storeNumber;
    }
    public void setStoreNumber(int storeNumber) {
        this.storeNumber = storeNumber;
    }
}

柜子中存儲(chǔ)的是數(shù)字藻肄。

然后我們把3個(gè)用戶抽象成一個(gè)類,如下代碼

/**
 * @author kdaddy@163.com
 * @date 2020/11/7 22:03
 */
public class User {
    // 柜子
    private Cabinet cabinet;
    // 存儲(chǔ)的數(shù)字
    private int storeNumber;

    public User(Cabinet cabinet, int storeNumber) {
        this.cabinet = cabinet;
        this.storeNumber = storeNumber;
    }
    // 表示使用柜子
    public void useCabinet(){
        cabinet.setStoreNumber(storeNumber);
    }
}

在用戶的構(gòu)造方法中拒担,需要傳入兩個(gè)參數(shù)嘹屯,一個(gè)是要使用的柜子,另一個(gè)是要存儲(chǔ)的數(shù)字从撼。以上我們把柜子和用戶都已經(jīng)抽象完畢州弟,接下來(lái)我們?cè)賮?lái)寫一個(gè)啟動(dòng)類,模擬一下3個(gè)用戶使用柜子的場(chǎng)景低零。

/**
 * @author kdaddy@163.com
 * @date 2020/11/7 22:05
 */
public class Starter {
    public static void main(String[] args) {
        final Cabinet cabinet = new Cabinet();
        ExecutorService es = Executors.newFixedThreadPool(3);

        for(int i= 1; i < 4; i++){
            final int storeNumber = i;
            es.execute(()->{
                User user = new User(cabinet,storeNumber);
                user.useCabinet();
                System.out.println("我是用戶"+storeNumber+",我存儲(chǔ)的數(shù)字是:"+cabinet.getStoreNumber());
            });
        }
        es.shutdown();
    }
}

我們仔細(xì)的看一下這個(gè)main函數(shù)的過(guò)程

  • 首先創(chuàng)建一個(gè)柜子的實(shí)例婆翔,由于場(chǎng)景中只有一個(gè)柜子,所以我們只創(chuàng)建了一個(gè)柜子實(shí)例掏婶。
  • 然后我們新建了一個(gè)線程池啃奴,線程池中一共有三個(gè)線程,每個(gè)線程執(zhí)行一個(gè)用戶的操作雄妥。
  • 再來(lái)看看每個(gè)線程具體的執(zhí)行過(guò)程最蕾,新建用戶實(shí)例,傳入的是用戶使用的柜子茎芭,我們這里只有一個(gè)柜子揖膜,所以傳入這個(gè)柜子的實(shí)例,然后傳入這個(gè)用戶所需要存儲(chǔ)的數(shù)字梅桩,分別是1,2,3壹粟,也分別對(duì)應(yīng)了用戶1,2,3。
  • 再調(diào)用使用柜子的操作宿百,也就是想柜子中放入要存儲(chǔ)的數(shù)字趁仙,然后立刻從柜子中取出數(shù)字,并打印出來(lái)垦页。

我們運(yùn)行一下main函數(shù)雀费,看看得到的打印結(jié)果是什么?

我是用戶1,我存儲(chǔ)的數(shù)字是:3
我是用戶3,我存儲(chǔ)的數(shù)字是:3
我是用戶2,我存儲(chǔ)的數(shù)字是:2

從結(jié)果中痊焊,我們可以看出三個(gè)用戶在存儲(chǔ)數(shù)字的時(shí)候兩個(gè)都是3盏袄,一個(gè)是2忿峻。這是為什么呢?我們期待的應(yīng)該是每個(gè)人都能獲取不同的數(shù)字才對(duì)辕羽。其實(shí)問(wèn)題就是出在"user.useCabinet();"這個(gè)方法上逛尚,這是因?yàn)楣褡舆@個(gè)實(shí)例沒(méi)有加鎖的原因,三個(gè)用戶并行執(zhí)行刁愿,向柜子中存儲(chǔ)他們的數(shù)字绰寞,雖然3個(gè)用戶并行同時(shí)操作铣口,但是在具體賦值的時(shí)候,也是有順序的件缸,因?yàn)樽兞縮toreNumber只有一塊內(nèi)存旭蠕,storeNumber只存儲(chǔ)一個(gè)值旷坦,存儲(chǔ)最后的線程所設(shè)置的值秒梅。至于哪個(gè)線程排在最后,則完全不確定疮丛,賦值語(yǔ)句執(zhí)行完成之后辆它,進(jìn)入打印語(yǔ)句疯溺,打印語(yǔ)句取storeNumber的值并打印飒筑,這時(shí)storeNumber存儲(chǔ)的是最后一個(gè)線程鎖所設(shè)置的值协屡,3個(gè)線程取到的值有兩個(gè)是相同的,就像上面打印的結(jié)果一樣肤晓。

那么如何才能解決這個(gè)問(wèn)題认然?這就需要我們用到鎖漫萄。我們?cè)儋x值語(yǔ)句上加鎖,這樣當(dāng)多個(gè)線程(此處表示用戶)同時(shí)賦值的時(shí)候卷胯,誰(shuí)能優(yōu)先搶到這把鎖窑睁,誰(shuí)才能夠賦值挺峡,這樣保證同一個(gè)時(shí)刻只能有一個(gè)線程進(jìn)行賦值操作担钮,避免了之前的混亂的情況箫津。

那么在程序中,我們?nèi)绾渭渔i呢饼拍?

下面我們介紹一下Java中的一個(gè)關(guān)鍵字synchronized田炭。關(guān)于這個(gè)關(guān)鍵字教硫,其實(shí)有兩種用法。

  • synchronized方法茶鉴,顧名思義就是把synchronize的關(guān)鍵字寫在方法上景用,它表示這個(gè)方法是加了鎖的丛肢,當(dāng)多個(gè)線程同時(shí)調(diào)用這個(gè)方法的時(shí)候,只有獲得鎖的線程才能夠執(zhí)行蜂怎,具體如下:

    public synchronized String getTicket(){
            return "xxx";
        }
    

    以上我們可以看到getTicket()方法加了鎖杠步,當(dāng)多個(gè)線程并發(fā)執(zhí)行的時(shí)候氢伟,只有獲得鎖的線程才可以執(zhí)行榜轿,其他的線程只能夠等待。

  • synchronized代碼塊朵锣。如下:

    synchronized (對(duì)象鎖){
        ……
    }
    

    我們將需要加鎖的語(yǔ)句都寫在代碼塊中,而在對(duì)象鎖的位置诚些,需要填寫加鎖的對(duì)象,它的含義是诬烹,當(dāng)多個(gè)線程并發(fā)執(zhí)行的時(shí)候砸烦,只有獲得你寫的這個(gè)對(duì)象的鎖绞吁,才能夠執(zhí)行后面的語(yǔ)句幢痘,其他的線程只能等待。synchronized塊通常的寫法是synchronized(this)家破,這個(gè)this是當(dāng)前類的實(shí)例颜说,也就是說(shuō)獲得當(dāng)前這個(gè)類的對(duì)象的鎖,才能夠執(zhí)行這個(gè)方法汰聋,此寫法等同于synchronized方法。

回到剛才的例子中马僻,我們又是如何解決storeNumber混亂的問(wèn)題呢韭邓?咱們?cè)囍诜椒ㄉ霞由湘i,這樣保證同時(shí)只有一個(gè)線程能調(diào)用這個(gè)方法女淑,具體如下。

/**
 * @author kdaddy@163.com
 * @date 2020/12/2 23:13
 */
public class Cabinet {
    //表示柜子中存放的數(shù)字
    private int storeNumber;

    public int getStoreNumber() {
        return storeNumber;
    }

    public synchronized void setStoreNumber(int storeNumber) {
        this.storeNumber = storeNumber;
    }
}

我們運(yùn)行一下代碼擒权,結(jié)果如下

我是用戶2,我存儲(chǔ)的數(shù)字是:2
我是用戶3,我存儲(chǔ)的數(shù)字是:2
我是用戶1,我存儲(chǔ)的數(shù)字是:1

我們發(fā)現(xiàn)結(jié)果還是混亂的袱巨,并沒(méi)有解決問(wèn)題愉老。我們檢查一下代碼

 es.execute(()->{
                User user = new User(cabinet,storeNumber);
                user.useCabinet();
                System.out.println("我是用戶"+storeNumber+",我存儲(chǔ)的數(shù)是:"+cabinet.getStoreNumber());
            });

我們可以看到在useCabinet和打印的方法是兩個(gè)語(yǔ)句剖效,并沒(méi)有保持原子性嫉入,雖然在set方法上加了鎖焰盗,但是在打印的時(shí)候又存在了并發(fā),打印語(yǔ)句是有鎖的咒林,但是不能確定哪個(gè)線程去執(zhí)行熬拒。所以這里,我們要保證useCabinet和打印的方法的原子性垫竞,我們使用synchronized塊澎粟,但是synchronized塊里的對(duì)象我們使用誰(shuí)的?這又是一個(gè)問(wèn)題欢瞪,user還是cabinet?回答當(dāng)然是cabinet捌议,因?yàn)槊總€(gè)線程都初始化了user,總共有3個(gè)User對(duì)象引有,而cabinet對(duì)象只有一個(gè)瓣颅,所以synchronized要用cabine對(duì)象,具體代碼如下

/**
 * @author kdaddy@163.com
 * @date 2020/12/7 22:05
 */
public class Starter {
    public static void main(String[] args) {
        final Cabinet cabinet = new Cabinet();
        ExecutorService es = Executors.newFixedThreadPool(3);

        for(int i= 1; i < 4; i++){
            final int storeNumber = i;
            es.execute(()->{
                User user = new User(cabinet,storeNumber);
                synchronized (cabinet){
                    user.useCabinet();
                    System.out.println("我是用戶"+storeNumber+",我存儲(chǔ)的數(shù)字是:"+cabinet.getStoreNumber());
                }
            });
        }
        es.shutdown();
    }
}

此時(shí)我們?cè)偃ミ\(yùn)行一下:

我是用戶3,我存儲(chǔ)的數(shù)字是:3
我是用戶2,我存儲(chǔ)的數(shù)字是:2
我是用戶1,我存儲(chǔ)的數(shù)字是:1

由于我們加了synchronized塊譬正,保證了存儲(chǔ)和取出的原子性宫补,這樣用戶存儲(chǔ)的數(shù)字和取出的數(shù)字就對(duì)應(yīng)上了,不會(huì)造成混亂曾我,最后我們用圖來(lái)表示一下上面例子的整體情況粉怕。


最終模型

如上圖所示,線程A抒巢,線程B贫贝,線程C同時(shí)調(diào)用Cabinet類的setStoreNumber方法,線程B獲得了鎖蛉谜,所以線程B可以執(zhí)行setStore的方法稚晚,線程A和線程C只能等待。

總結(jié)

通過(guò)上面的場(chǎng)景以及例子型诚,我們可以了解多線程情況下客燕,造成的變量值前后不一致的問(wèn)題,以及鎖的作用狰贯,在使用了鎖以后也搓,可以避免這種混亂的現(xiàn)象,后續(xù)涵紊,老貓會(huì)和大家介紹一個(gè)Java中都有哪些關(guān)于鎖的解決方案傍妒,以及項(xiàng)目中所用到的實(shí)戰(zhàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摸柄,一起剝皮案震驚了整個(gè)濱河市颤练,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塘幅,老刑警劉巖昔案,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尿贫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡踏揣,警方通過(guò)查閱死者的電腦和手機(jī)庆亡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捞稿,“玉大人又谋,你說(shuō)我怎么就攤上這事∮榫郑” “怎么了彰亥?”我有些...
    開(kāi)封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)衰齐。 經(jīng)常有香客問(wèn)我任斋,道長(zhǎng),這世上最難降的妖魔是什么耻涛? 我笑而不...
    開(kāi)封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任废酷,我火速辦了婚禮,結(jié)果婚禮上抹缕,老公的妹妹穿的比我還像新娘澈蟆。我一直安慰自己,他們只是感情好卓研,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布趴俘。 她就那樣靜靜地躺著,像睡著了一般奏赘。 火紅的嫁衣襯著肌膚如雪寥闪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天志珍,我揣著相機(jī)與錄音橙垢,去河邊找鬼。 笑死伦糯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嗽元。 我是一名探鬼主播敛纲,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼剂癌!你這毒婦竟也來(lái)了淤翔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤佩谷,失蹤者是張志新(化名)和其女友劉穎旁壮,沒(méi)想到半個(gè)月后监嗜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抡谐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年裁奇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麦撵。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刽肠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出免胃,到底是詐尸還是另有隱情音五,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布羔沙,位于F島的核電站躺涝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扼雏。R本人自食惡果不足惜诞挨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呢蛤。 院中可真熱鬧惶傻,春花似錦、人聲如沸其障。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)励翼。三九已至蜈敢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汽抚,已是汗流浹背抓狭。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留造烁,地道東北人否过。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惭蟋,于是被迫代替她去往敵國(guó)和親苗桂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容