Java基礎(chǔ)-鎖

????為了提高系統(tǒng)的資源利用率着逐,促使了進(jìn)程倘潜,線程的出現(xiàn)砚婆。進(jìn)程和線程提高了系統(tǒng)CPU利用率的同時(shí)械拍,又引出了一些其他的問題。
????這里僅討論線程安全性的問題装盯,因?yàn)槎鄠€(gè)線程中操作執(zhí)行順序是不可預(yù)測(cè)的坷虑,甚至?xí)a(chǎn)生一些奇怪的結(jié)果。


多線程造成的安全性問題

下面通過幾個(gè)簡單的示例來看一下埂奈,在沒有鎖的情況下會(huì)存在什么問題迄损。
1、錯(cuò)誤的單例模式

public class SingleInstance {
    // 實(shí)例對(duì)象
    private static Object instance;
    
    /**
     * 獲取該對(duì)象的實(shí)例
     */
    public Object getInstance() {
        if (instance == null) {
           instance = new Object();
        }
        return instance;
    }
}

????該類提供的方法本意是账磺,多次請(qǐng)求getInstance()方法芹敌,instance對(duì)象只初始化一次。在單線程的情況下貌似沒什么問題垮抗,但是在多線程的情況下党窜,就會(huì)存在問題了。
????假定線程A和線程B同時(shí)調(diào)用getInstance方法借宵,A線程判斷instance對(duì)象當(dāng)前為null,實(shí)例化了一個(gè)對(duì)象矾削。在A線程沒有實(shí)例化完之前壤玫,執(zhí)行了線程調(diào)度,B線程也訪問當(dāng)了if的判斷條件中哼凯,發(fā)現(xiàn)instance也為null欲间,也實(shí)例化了一個(gè)對(duì)象并賦值給了instance。最終結(jié)果就是A断部,B線程拿到了不同的對(duì)象實(shí)例猎贴。
2、共享變量被訪問

public class Counter{
   private integer count = 0;
    
   public static Integer addCount() {
        return count++;
   }
}

????上述是一個(gè)技術(shù)器類,它提供了統(tǒng)計(jì)每個(gè)線程訪問服務(wù)器次數(shù)的作用她渴。但是當(dāng)多個(gè)線程同時(shí)并發(fā)訪問达址,它的統(tǒng)計(jì)出來的數(shù)據(jù)就會(huì)存在誤差。為什么會(huì)有這個(gè)問題呢趁耗?
????count++并不是一個(gè)原子操作沉唠,它主要包含這幾步,讀取count變量在內(nèi)存中的值苛败,將count值加一满葛,得到的結(jié)果再賦值給count變量,這是一個(gè) 讀取 -> 修改 -> 寫入罢屈。假設(shè)現(xiàn)在有兩個(gè)線程A和B嘀韧,當(dāng)前count變量的值為1,A線程對(duì)count變量進(jìn)行了讀取缠捌,修改的操作锄贷,在沒有執(zhí)行寫入操作之前發(fā)生了系統(tǒng)調(diào)度將線程A掛起,線程B開始執(zhí)行自己的操作鄙币,將B線程讀取了count變量的值(count = 1)肃叶,并進(jìn)行了后續(xù)的操作,將計(jì)算的count = 2的結(jié)果賦值給了count十嘿,執(zhí)行完畢因惭。線程A繼續(xù)執(zhí)行,此時(shí)同樣將count = 2 寫入到count中绩衷。這樣得到最后的結(jié)果count就會(huì)少記了一次線程的訪問蹦魔。


如何保證線程安全性

????先來看一下線程安全是如何定義的?
????在線程安全性的定義中要求咳燕,多個(gè)線程之間的操作無論采用何種執(zhí)行時(shí)序或交替方式勿决,都要保證不變性條件不被破壞。
????Java中提供了鎖來保證線程安全性招盲,通過在指定的代碼塊中添加synchronized或ReentrantLock關(guān)鍵字來保證操作的原子性低缩。
????當(dāng)線程要訪問一個(gè)被加鎖的對(duì)象、方法或者代碼塊時(shí)曹货,會(huì)自動(dòng)獲得鎖咆繁,如果當(dāng)前的鎖被其他線程持有,則會(huì)將線程先掛起顶籽,等持有鎖的線程將鎖被釋放后會(huì)發(fā)起信號(hào)喚醒阻塞中的線程玩般,去搶占該鎖。


synchronized的用法

????synchronized是Java提供的一種內(nèi)置鎖礼饱,synchronized的用法可以分為三種
????1. 修飾類
????2. 修飾方法(靜態(tài)方法|非靜態(tài)方法)
????3. 修飾代碼塊(變量)

  1. 修飾類
public class SynchronizedClass{

    public static Object instance = null;

     public Object getInstance() {
         synchronized(SynchronizedClass.class) {
             if (null == instance) {
               return new Instance();
             }
             return instance;
         }
     }
}

當(dāng)synchronized修飾類時(shí)坏为,不管是訪問SynchronizedClass類的哪個(gè)實(shí)例對(duì)象究驴,所有線程都會(huì)競(jìng)爭(zhēng)同一把鎖。

  1. 修飾靜態(tài)方法
public class LockStaticMethod{

    public static Object instance = null;

     public synchronized static Object getInstance() {
            if (null == instance) {
               return new Instance();
            }
            return instance;
     }
}

當(dāng)synchronized修飾靜態(tài)方法時(shí)匀伏,不管是訪問LockStaticMethod類的哪個(gè)實(shí)例對(duì)象中的getInstance方法洒忧,所有線程都會(huì)競(jìng)爭(zhēng)同一把鎖。

  1. 修飾非靜態(tài)方法
public class LockMethod{

    public static Object instance = null;

     public synchronized Object getInstance() {
            if (null == instance) {
               return new Instance();
            }
            return instance;
     }
}

當(dāng)synchronized修飾非靜態(tài)方法時(shí)帘撰,不同實(shí)例對(duì)象持有的鎖是相互不影響的跑慕。

  1. 修飾代碼塊(變量)
public class LockVariable{

    public static Object instance = null;

     public synchronized Object getInstance() {
            synchronized(instance) {
              if (null == instance) {
                 return new Instance();
              }
              return instance;
           }
     }
}

synchronized修飾變量同樣也分靜態(tài)變量和非靜態(tài)變量,他們的含義同靜態(tài)方法和非靜態(tài)方法類似摧找。
下面看一下核行,當(dāng)線程訪問一個(gè)被加鎖的方法時(shí),執(zhí)行過程是怎樣的蹬耘。


image.png

ReentrantLock的用法

????ReentrantLock類是Java 5.0才出現(xiàn)的新的加鎖機(jī)制芝雪,ReentrantLock相比于synchronized提供的加鎖機(jī)制更加靈活,并且提供了一些synchronized不具備的功能综苔。例如:可中斷的鎖獲取操作惩系、公平隊(duì)列、非塊結(jié)構(gòu)的鎖如筛,可定時(shí)的鎖堡牡,可輪訓(xùn)的鎖。下面我們來看一下這些特性的使用示例杨刨。

  1. 可中斷的鎖
    可中斷的鎖提供了對(duì)可取消任務(wù)加鎖的需求晤柄,具體使用方式如下所示
    private ReentrantLock lock = new ReentrantLock();

    public void interruptedLock() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            throw new InterruptedException();
        }finally {
            lock.unlock();
        }
    }
  1. 公平隊(duì)列
    ReentrantLock提供了公平鎖,線程按照它們發(fā)出請(qǐng)求的順序來獲得鎖妖胀。ReentrantLock類提供了ReentrantLock(boolean fair)構(gòu)造函數(shù)來初始化公平鎖芥颈。使用時(shí)只要將參數(shù)設(shè)置為true即可。
ReentrantLock fairLock = new ReentrantLock(true);
  1. 非塊結(jié)構(gòu)的鎖

  1. 可定時(shí)的鎖
    ReentrantLock提供了定時(shí)的方法tryLock(long timeout, TimeUnit unit)赚抡,他能根據(jù)剩余時(shí)間來提供一個(gè)時(shí)限爬坑,如果操作不能在指定的時(shí)間內(nèi)給出結(jié)果,那么就會(huì)使程序提前結(jié)束涂臣,示例如下:
public class TimeTryLock {

    public static void main(String[] args) throws Exception {
        ReentrantLock reentrantLock = new ReentrantLock();
        if (reentrantLock.tryLock(100L, TimeUnit.SECONDS)) {
            System.out.println("success");
        }
    }
}
  1. 可輪訓(xùn)的鎖
    tryLock方法實(shí)現(xiàn)了有條件的獲取鎖的模式盾计,與無條件的鎖獲取模式相比,它具有更完善的錯(cuò)誤恢復(fù)機(jī)制赁遗〈彻溃可輪訓(xùn)鎖提供了另一種選擇:避免發(fā)生死鎖。
    先來看一下在synchronized模式下死鎖的情況
public class SyncDeadLock {

    public void deadLock(Integer v1, Integer v2) {
        synchronized (v1) {
            System.out.println(Thread.currentThread() + "get object lock " + v1);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (v2) {
                System.out.println(Thread.currentThread() + "get object lock " + v2);
            }
        }
    }

    public static void main(String[] args) {
        Integer v1 = 0;
        Integer v2 = 2;
        SyncDeadLock syncDeadLock = new SyncDeadLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncDeadLock.deadLock(v1, v2);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncDeadLock.deadLock(v2, v1);
            }
        }).start();

    }
}

有了tryLock之后吼和,通過tryLock就可以有效的避免死鎖,tryLock可以嘗試獲取鎖骑素,如果沒有獲取到鎖炫乓,可以主動(dòng)將當(dāng)前持有的鎖釋放掉刚夺,這樣可以避免死鎖發(fā)生的條件,看下面的例子:

public class ReentrantLockDeadLock {

    private static Integer count = 10;

    public boolean tryLock(ReentrantLock lock1, ReentrantLock lock2) {
        while (true) {
            if (lock1.tryLock()) {
                try {
                    System.out.println(Thread.currentThread() + " get " + lock1);
                    if (lock2.tryLock()) {
                        try {
                            System.out.println(Thread.currentThread() + " get " + lock2);
                        } finally {
                            System.out.println(Thread.currentThread() + " unlock " + lock2);
                            lock2.unlock();
                            System.out.println("count = " + --count);
                            if (count < 0) {
                                return true;
                            }
                        }
                    }
                } finally {
                    System.out.println(Thread.currentThread() + " unlock " + lock1);
                    lock1.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLock lock1 = new ReentrantLock();
        ReentrantLock lock2 = new ReentrantLock();
        ReentrantLockDeadLock mainLock = new ReentrantLockDeadLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mainLock.tryLock(lock1, lock2);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                mainLock.tryLock(lock2, lock1);
            }
        }).start();

    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末末捣,一起剝皮案震驚了整個(gè)濱河市侠姑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箩做,老刑警劉巖莽红,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異邦邦,居然都是意外死亡安吁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門燃辖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鬼店,“玉大人,你說我怎么就攤上這事黔龟「局牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵氏身,是天一觀的道長巍棱。 經(jīng)常有香客問我,道長蛋欣,這世上最難降的妖魔是什么航徙? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮豁状,結(jié)果婚禮上捉偏,老公的妹妹穿的比我還像新娘。我一直安慰自己泻红,他們只是感情好夭禽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谊路,像睡著了一般讹躯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缠劝,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天潮梯,我揣著相機(jī)與錄音,去河邊找鬼惨恭。 笑死秉馏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脱羡。 我是一名探鬼主播萝究,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼免都,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了帆竹?” 一聲冷哼從身側(cè)響起绕娘,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栽连,沒想到半個(gè)月后险领,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秒紧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年绢陌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片噩茄。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡下面,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绩聘,到底是詐尸還是另有隱情沥割,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布凿菩,位于F島的核電站机杜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏衅谷。R本人自食惡果不足惜椒拗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望获黔。 院中可真熱鬧蚀苛,春花似錦、人聲如沸玷氏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盏触。三九已至渗蟹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赞辩,已是汗流浹背雌芽。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辨嗽,地道東北人世落。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像糟需,于是被迫代替她去往敵國和親岛心。 傳聞我的和親對(duì)象是個(gè)殘疾皇子来破,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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

  • 整理來自互聯(lián)網(wǎng) 1,JDK:Java Development Kit忘古,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)工具...
    Ncompass閱讀 1,537評(píng)論 0 6
  • 一:java概述: 1诅诱,JDK:Java Development Kit髓堪,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)...
    慕容小偉閱讀 1,788評(píng)論 0 10
  • 一:java概述:1娘荡,JDK:Java Development Kit干旁,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)工...
    ZaneInTheSun閱讀 2,649評(píng)論 0 11
  • 一炮沐、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個(gè)執(zhí)行中的程序?qū)嵗海總€(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程中可以有多個(gè)線程大年。...
    阿敏其人閱讀 2,612評(píng)論 0 13
  • 偶爾自己會(huì)覺得自己是個(gè)文藝青年翔试,會(huì)寫字轻要,能畫畫,興致來了可以搞搞插花垦缅,做做精致的菜肴冲泥,并在這些里體會(huì)快樂。 可偶爾...
    聽雨廿一閱讀 260評(píng)論 4 2