鎖與線程安全

1茉贡、Java中的兩種鎖

1、synchronized

synchronized 是一種對(duì)象鎖准潭,它以對(duì)象為鎖趁俊,在之前的筆記中有過詳細(xì)解釋。如果想要獲得唯一的對(duì)象作為鎖刑然,有兩種方法寺擂。一是在類中定義一個(gè)靜態(tài)的對(duì)象,二是直接用類名.class。

2怔软、Lock

Lock是一個(gè)接口垦细,一般用它的實(shí)現(xiàn)類ReentrantLock。以下是Lock的使用示例:

Lock lock = new ReentrantLock();
lock.lock();
//中間是需要同步的代碼
同步代碼
//這個(gè)類用來控制線程的等待和喚醒挡逼,由對(duì)應(yīng)Lock創(chuàng)建
Condition cd = lock.newCondition();
//這個(gè)方法類似synchronized中的wait 可以釋放對(duì)應(yīng)線程的鎖
cd.await();
//類似synchronized中的notify 喚醒指定線程
cd.signal();
//同理 Lock也有喚醒所有線程的方法
cd.signalAll();
lock.unlock();

Lock鎖的作用是lock()和unlock()之間括改,為了對(duì)不同線程狀態(tài)進(jìn)行管理,利用newCondition()生成Condition類家坎,一個(gè)Lock對(duì)象可以實(shí)例化多個(gè)Condition對(duì)象嘱能。

2、多線程之間的協(xié)調(diào)

在之前講到Lock類時(shí)虱疏,說到了一個(gè)Condition對(duì)象惹骂,實(shí)例化多個(gè)這種對(duì)象,可以用來對(duì)多線程進(jìn)行協(xié)調(diào)做瞪,不過在此之前先看一下synchronized是如何對(duì)兩個(gè)線程進(jìn)行協(xié)調(diào)的对粪。

1、synchronized實(shí)現(xiàn)兩個(gè)線程的交替執(zhí)行

public class MyMain {
    public static void main(String[] args) {
        Test test = new Test();
        Thread th0 = new Thread(() -> {
            test.test();
        });
        Thread th1 = new Thread(() -> {
            test.test();
        });
        th0.start();
        th1.start();
    }
}

class Test {
    private static int x = 1000;
    private Object obj = new Object();

    public void test() {
        synchronized (obj) {
            while (true) {
                try {
                    //鎖對(duì)象喚醒所有等待的線程
                    obj.notifyAll();
                    //線程到此等待并釋放鎖
                    obj.wait();
                    System.out.println(Thread.currentThread().getName() + "--" + x);
                    Thread.sleep(500);
                    x--;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

從同步代碼塊可以看出装蓬,拿到鎖的線程先喚醒所有等待的線程著拭,然后接下來自己進(jìn)入等待狀態(tài)并釋放鎖,等到另一個(gè)線程拿到鎖喚醒它牍帚,形成線程的循環(huán)交替執(zhí)行茫死。在這個(gè)同步代碼塊中,第一次喚醒是沒有意義的履羞,因?yàn)殒i被第一次拿到的時(shí)候峦萎,沒有其它線程在等待狀態(tài),同理當(dāng)一個(gè)線程被關(guān)閉忆首,另一個(gè)線程因?yàn)闆]有被喚醒爱榔,造成死鎖,不過這種情況下一般需要執(zhí)行的業(yè)務(wù)已經(jīng)結(jié)束了糙及,所以對(duì)整個(gè)業(yè)務(wù)沒有影響详幽。

2、Lock實(shí)現(xiàn)三個(gè)線程順序交替執(zhí)行

package com.fan.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestMain {
    public static void main(String[] args) {
        LockBean lockBean = new LockBean();
        ThrA thrA = new ThrA(lockBean);
        ThrB thrB = new ThrB(lockBean);
        ThrC thrC = new ThrC(lockBean);
        thrA.start();
        thrB.start();
        thrC.start();
    }
}
//將需要傳入的參數(shù)合并為一個(gè)類浸锨,簡化傳參
class LockBean {
    public Lock lock = new ReentrantLock();
    public String flag = "A";
    public Condition cd_a = lock.newCondition();
    public Condition cd_b = lock.newCondition();
    public Condition cd_c = lock.newCondition();
}

同時(shí)開啟三個(gè)線程唇聘,分別交替輸出A、B和C柱搜,這里以輸出A的線程為例:

package com.fan.test;

public class ThrA extends Thread {
    private LockBean lockBean;

    public ThrA(LockBean lockBean) {
        this.lockBean = lockBean;
    }

    @Override
    public void run() {
        while (true) {
            try {
                //開始加鎖
                lockBean.lock.lock();
                //參數(shù)對(duì)象中有一個(gè)flag字符串參數(shù)迟郎,用來判斷此時(shí)是否由該線程輸出
                if (!"A".equals(lockBean.flag)) {
                    //如果flag不是該線程輸出語句,就讓這個(gè)線程等待并釋放鎖
                    lockBean.cd_a.await();
                }
                System.out.println("A");
                lockBean.flag = "B";
                lockBean.cd_b.signal();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lockBean.lock.unlock();
        }
    }
}

當(dāng)flag不為A時(shí)聪蘸,利用Condition對(duì)象使線程等待宪肖;當(dāng)flag為A時(shí)表制,打印輸出A并將flag變?yōu)橄乱粋€(gè)要輸出的字符串(這里是B),然后將利用下一個(gè)線程的Condition對(duì)象將其喚醒控乾。所以么介, 每次輸出時(shí),只有對(duì)應(yīng)的線程在工作蜕衡,并且輸出完畢會(huì)更改flag壤短,使得對(duì)應(yīng)線程被喚醒,而無關(guān)線程等待慨仿。

3久脯、鎖的類型

1、公平/非公平鎖

每個(gè)線程在獲取鎖的時(shí)候镶骗,都是隨機(jī)獲取的,這種鎖叫非公平鎖躲雅。如果按照線程等待的前后順序獲得鎖鼎姊,那么就是公平鎖。在
Java中相赁,synchronized一定是非公平鎖相寇,而Lock可以設(shè)置成公平鎖或非公平鎖。

2钮科、自旋鎖

當(dāng)鎖被一個(gè)線程占用的唤衫,其他線程持續(xù)不斷的嘗試獲得鎖,這種就叫自旋鎖绵脯;如果是非自旋鎖佳励,那么其它線程會(huì)進(jìn)入阻塞狀態(tài),
等鎖被釋放的時(shí)候蛆挫,被喚醒赃承,進(jìn)入就緒啟動(dòng)狀態(tài),再去獲得鎖悴侵。線程在切換狀態(tài)的過程中瞧剖,會(huì)降低線程的執(zhí)行速度;而自旋鎖的
線程會(huì)一直占用內(nèi)存以持續(xù)嘗試獲得鎖可免。

所以抓于,當(dāng)一個(gè)鎖可能會(huì)被長時(shí)間持有,那就使用非自旋鎖浇借,降低系統(tǒng)性能的消耗捉撮;如果鎖被持有的時(shí)間不會(huì)太長的話,建議使用自旋鎖妇垢,以防止線程狀態(tài)切換而降低線程執(zhí)行效率呕缭。

3堵泽、悲觀鎖和樂觀鎖

悲觀鎖就是指,持有鎖的線程在執(zhí)行過程中恢总,悲觀地認(rèn)為一定會(huì)有其他線程干預(yù)迎罗,所以在使用共享資源前一定會(huì)先加鎖,一直等
到這個(gè)線程釋放鎖片仿,比如Java中的synchronized和ReentrantLock纹安。
而樂觀鎖則是指持有鎖的線程在執(zhí)行過程中,樂觀地認(rèn)為不會(huì)有其它線程干預(yù)砂豌,所以不會(huì)對(duì)數(shù)據(jù)上鎖厢岂,只有在更新數(shù)據(jù)的時(shí)候才
會(huì)確認(rèn)數(shù)據(jù)是不是被其它線程改變了,例如Java中的原子變量類阳距。

樂觀鎖適用于讀比較多塔粒,寫比較少的情形,這樣可以減少鎖的開銷筐摘,而如果是相反的情況卒茬,寫操作會(huì)使的鎖不停地檢查,而且一旦反生數(shù)據(jù)不一致咖熟,那么就會(huì)重新執(zhí)行沖突數(shù)據(jù)的相關(guān)操作圃酵,所以在寫比較多的時(shí)候一般使用悲觀鎖。一般高級(jí)語言無法直接實(shí)現(xiàn)樂觀鎖馍管,這需要底層對(duì)CAS的支持實(shí)現(xiàn)郭赐。在數(shù)據(jù)庫中,可以原生支持樂觀鎖确沸,具體是在數(shù)據(jù)中增加一個(gè)版本號(hào)的屬性捌锭。在查詢數(shù)據(jù)的時(shí)候,會(huì)一并讀出這個(gè)數(shù)據(jù)的版本號(hào)罗捎,當(dāng)發(fā)生非查詢操作時(shí)舀锨,都會(huì)檢查操作數(shù)據(jù)的版本號(hào)與當(dāng)前數(shù)據(jù)數(shù)據(jù)庫的版本是否一致,如果相同則版本號(hào)加一宛逗,并提交更新數(shù)據(jù)坎匿,如果不相同,則認(rèn)為是過期數(shù)據(jù)雷激,不予提交本次更新操作并重試替蔬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屎暇,隨后出現(xiàn)的幾起案子承桥,更是在濱河造成了極大的恐慌,老刑警劉巖根悼,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凶异,死亡現(xiàn)場離奇詭異蜀撑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)剩彬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門酷麦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喉恋,你說我怎么就攤上這事沃饶。” “怎么了轻黑?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵糊肤,是天一觀的道長。 經(jīng)常有香客問我氓鄙,道長馆揉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任抖拦,我火速辦了婚禮升酣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蟋座。我一直安慰自己拗踢,他們只是感情好脚牍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布向臀。 她就那樣靜靜地躺著,像睡著了一般诸狭。 火紅的嫁衣襯著肌膚如雪券膀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天驯遇,我揣著相機(jī)與錄音芹彬,去河邊找鬼。 笑死叉庐,一個(gè)胖子當(dāng)著我的面吹牛舒帮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陡叠,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玩郊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枉阵?” 一聲冷哼從身側(cè)響起译红,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兴溜,沒想到半個(gè)月后侦厚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耻陕,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年刨沦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诗宣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡已卷,死狀恐怖梧田,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侧蘸,我是刑警寧澤裁眯,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站讳癌,受9級(jí)特大地震影響穿稳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晌坤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一逢艘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骤菠,春花似錦它改、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹉戚,卻和暖如春鲜戒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抹凳。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工遏餐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赢底。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓失都,卻偏偏與公主長得像,于是被迫代替她去往敵國和親幸冻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子粹庞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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