Synchronize和ReentrantLock區別

楊充發表於2018-10-18

目錄介紹

  • 1.Synchronize和ReentrantLock區別
    • 1.1 相似點
    • 1.2 區別
    • 1.3 什麼是執行緒安全問題?如何理解
    • 1.4 執行緒安全需要保證幾個基本特性
  • 2.Synchronize在編譯時如何實現鎖機制
  • 3.ReentrantLock使用方法
  • 4.ReentrantLock鎖機制測試案例分析
    • 4.1 程式碼案例分析
    • 4.2 什麼時候選擇用ReentrantLock
    • 4.3 公平鎖和非公平鎖有何區別
  • 5.問答測試題
    • 5.1 ReentrantLock和synchronized使用分析

好訊息

  • 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
  • 連結地址:github.com/yangchong21…
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!

關於鎖機制文章

  • 01.Synchronize深入解析
    • Synchronize深入解析,sychonized method 和 synchonized程式碼塊的效率問題
  • 02.Synchronize和ReentrantLock區別
    • Synchronize和ReentrantLock區別,Synchronize在編譯時如何實現鎖機制,ReentrantLock鎖機制測試案例分析,公平鎖和非公平鎖有何區別等等
  • 03.死鎖的發生,定位與修復
    • 死鎖的概念和產生死鎖的根本原因是什麼?死鎖的預防策略中資源有序分配策略是什麼。死鎖發生的場景,死鎖的危害,出現死鎖需要滿足條件分析,如何預防死鎖,如何定位死鎖,以及死鎖修復方案分析等等

1.Synchronize和ReentrantLock區別

1.1 相似點:

  • 這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當如果一個執行緒獲得了物件鎖,進入了同步塊,其他訪問該同步塊的執行緒都必須阻塞在同步塊外面等待,而進行執行緒阻塞和喚醒的代價是比較高的(作業系統需要在使用者態與核心態之間來回切換,代價很高,不過可以通過對鎖優化進行改善)。

1.2 區別:

1.2.1 API層面
  • 這兩種方式最大區別就是對於Synchronized來說,它是java語言的關鍵字,是原生語法層面的互斥,需要jvm實現。而ReentrantLock它是JDK 1.5之後提供的API層面的互斥鎖,需要lock()和unlock()方法配合try/finally語句塊來完成。
  • synchronized既可以修飾方法,也可以修飾程式碼塊。
    //synchronized修飾一個方法時,這個方法叫同步方法。
    public synchronized void test() {
    //方法體``
    
    }
    
    synchronized(Object) {
    //括號中表示需要鎖的物件.
    //執行緒執行的時候會對Object上鎖
    }
    複製程式碼
  • ReentrantLock使用
    private ReentrantLock lock = new ReentrantLock();
    public void run() {
        lock.lock();
        try{
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }finally{
            lock.unlock();
        }
    }
    複製程式碼
1.2.2 等待可中斷
  • 等待可中斷是指當持有鎖的執行緒長期不釋放鎖的時候,正在等待的執行緒可以選擇放棄等待,改為處理其他事情。可等待特性對處理執行時間非常長的同步快很有幫助。
  • 具體來說,假如業務程式碼中有兩個執行緒,Thread1 Thread2。假設 Thread1 獲取了物件object的鎖,Thread2將等待Thread1釋放object的鎖。
    • 使用synchronized。如果Thread1不釋放,Thread2將一直等待,不能被中斷。synchronized也可以說是Java提供的原子性內建鎖機制。內部鎖扮演了互斥鎖(mutual exclusion lock ,mutex)的角色,一個執行緒引用鎖的時候,別的執行緒阻塞等待。
    • 使用ReentrantLock。如果Thread1不釋放,Thread2等待了很長時間以後,可以中斷等待,轉而去做別的事情。
1.2.3 公平鎖
  • 公平鎖是指多個執行緒在等待同一個鎖時,必須按照申請的時間順序來依次獲得鎖;而非公平鎖則不能保證這一點。非公平鎖在鎖被釋放時,任何一個等待鎖的執行緒都有機會獲得鎖。
  • synchronized的鎖是非公平鎖,ReentrantLock預設情況下也是非公平鎖,但可以通過帶布林值的建構函式要求使用公平鎖。
    • ReentrantLock 構造器的一個引數是boolean值,它允許您選擇想要一個公平(fair)鎖,還是一個不公平(unfair)鎖。公平鎖:使執行緒按照請求鎖的順序依次獲得鎖, 但是有成本;不公平鎖:則允許討價還價
    • 那麼如何用程式碼設定公平鎖呢?如下所示
    • image
1.2.4 鎖繫結多個條件
  • ReentrantLock可以同時繫結多個Condition物件,只需多次呼叫newCondition方法即可。
  • synchronized中,鎖物件的wait()和notify()或notifyAll()方法可以實現一個隱含的條件。但如果要和多於一個的條件關聯的時候,就不得不額外新增一個鎖。

1.3 什麼是執行緒安全問題?如何理解

  • 如果你的程式碼所在的程式中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的,或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題 。

1.4 執行緒安全需要保證幾個基本特性

  • 1、原子性,簡單說就是相關操作不會中途被其他執行緒干擾,一般通過同步機制實現。
  • 2、可見性,是一個執行緒修改了某個共享變數,其狀態能夠立即被其他執行緒知曉,通常被解釋為將執行緒本地狀態反映到主記憶體上,volatile 就是負責保證可見性的。
  • 3、有序性,是保證執行緒內序列語義,避免指令重排等。

2.Synchronize在編譯時如何實現鎖機制

  • Synchronized進過編譯,會在同步塊的前後分別形成monitorenter和monitorexit這個兩個位元組碼指令。在執行monitorenter指令時,首先要嘗試獲取物件鎖。如果這個物件沒被鎖定,或者當前執行緒已經擁有了那個物件鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器為0時,鎖就被釋放了。如果獲取物件鎖失敗,那當前執行緒就要阻塞,直到物件鎖被另一個執行緒釋放為止。

3.ReentrantLock使用方法

  • ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高階功能,主要有以下3項:
    • 1.等待可中斷,持有鎖的執行緒長期不釋放的時候,正在等待的執行緒可以選擇放棄等待,這相當於Synchronized來說可以避免出現死鎖的情況。
    • 2.公平鎖,多個執行緒等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock預設的建構函式是建立的非公平鎖,可以通過引數true設為公平鎖,但公平鎖表現的效能不是很好。
    • 3.鎖繫結多個條件,一個ReentrantLock物件可以同時繫結對個物件。
  • 使用方法程式碼如下
    private ReentrantLock lock = new ReentrantLock();
    public void run() {
        lock.lock();
        try{
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }finally{
            lock.unlock();
        }
    }
    複製程式碼
  • 注意問題:為保證鎖釋放,每一個 lock() 動作,建議都立即對應一都立即對應一個 try-catch-finally

4.ReentrantLock鎖機制測試案例分析

4.1 程式碼案例分析

  • 程式碼如下所示
    private void test2() {
        Runnable t1 = new MyThread();
        new Thread(t1,"t1").start();
        new Thread(t1,"t2").start();
    }
    
    class MyThread implements Runnable {
        private ReentrantLock lock = new ReentrantLock();
        public void run() {
            lock.lock();
            try{
                for(int i=0;i<5;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }finally{
                lock.unlock();
            }
        }
    }
    
    //列印值如下所示
    10-17 17:06:59.222 6531-6846/com.yc.cn.ycbaseadapter I/System.out: t1:0
    10-17 17:06:59.222 6531-6846/com.yc.cn.ycbaseadapter I/System.out: t1:1
    10-17 17:06:59.222 6531-6846/com.yc.cn.ycbaseadapter I/System.out: t1:2
    10-17 17:06:59.222 6531-6846/com.yc.cn.ycbaseadapter I/System.out: t1:3
    10-17 17:06:59.222 6531-6846/com.yc.cn.ycbaseadapter I/System.out: t1:4
    10-17 17:06:59.224 6531-6847/com.yc.cn.ycbaseadapter I/System.out: t2:0
    10-17 17:06:59.225 6531-6847/com.yc.cn.ycbaseadapter I/System.out: t2:1
    10-17 17:06:59.225 6531-6847/com.yc.cn.ycbaseadapter I/System.out: t2:2
    10-17 17:06:59.225 6531-6847/com.yc.cn.ycbaseadapter I/System.out: t2:3
    10-17 17:06:59.225 6531-6847/com.yc.cn.ycbaseadapter I/System.out: t2:4
    複製程式碼

4.2 什麼時候選擇用ReentrantLock

  • 適用場景:時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變數或者鎖投票
  • 在確實需要一些 synchronized所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變數或者鎖投票。 ReentrantLock 還具有可伸縮性的好處,應當在高度爭用的情況下使用它,但是請記住,大多數 synchronized 塊幾乎從來沒有出現過爭用,所以可以把高度爭用放在一邊。我建議用 synchronized 開發,直到確實證明 synchronized 不合適,而不要僅僅是假設如果使用 ReentrantLock “效能會更好”。請記住,這些是供高階使用者使用的高階工具。(而且,真正的高階使用者喜歡選擇能夠找到的最簡單工具,直到他們認為簡單的工具不適用為止。)。一如既往,首先要把事情做好,然後再考慮是不是有必要做得更快。
  • 使用場景程式碼展示【摘自ThreadPoolExecutor類,這個類中很多地方用到了這個鎖。自己可以檢視】:
    /**
     * Rolls back the worker thread creation.
     * - removes worker from workers, if present
     * - decrements worker count
     * - rechecks for termination, in case the existence of this
     *   worker was holding up termination
     */
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }
    複製程式碼

4.3 公平鎖和非公平鎖有何區別

  • 公平性是指在競爭場景中,當公平性為真時,會傾向於將鎖賦予等待時間最久的執行緒。公平性是減少執行緒“飢餓”(個別執行緒長期等待鎖,但始終無法獲取)情況發生的一個辦法。
    • 1、公平鎖能保證:老的執行緒排隊使用鎖,新執行緒仍然排隊使用鎖。
    • 2、非公平鎖保證:老的執行緒排隊使用鎖;但是無法保證新執行緒搶佔已經在排隊的執行緒的鎖。
    • 看下面程式碼案例所示:可以得出結論,公平鎖指的是哪個執行緒先執行,那就可以先得到鎖。非公平鎖是不管執行緒是否是先執行,新的執行緒都有可能搶佔已經在排隊的執行緒的鎖。
    private void test3() {
        Service service = new Service();
        ThreadClass tcArray[] = new ThreadClass[10];
        for(int i=0;i<10;i++){
            tcArray[i] = new ThreadClass(service);
            tcArray[i].start();
        }
    }
    
    public class Service {
        ReentrantLock lock = new ReentrantLock(true);
        Service() {
        }
    
        void getThreadName() {
            System.out.println(Thread.currentThread().getName() + " 已經被鎖定");
        }
    }
    public class ThreadClass extends Thread{
        private Service service;
        ThreadClass(Service service) {
            this.service = service;
        }
        public void run(){
            System.out.println(Thread.currentThread().getName() + " 搶到了鎖");
            service.lock.lock();
            service.getThreadName();
            service.lock.unlock();
        }
    }
    //當ReentrantLock設定true,也就是公平鎖時
    10-17 19:32:22.422 6459-6523/com.yc.cn.ycbaseadapter I/System.out: Thread-5 搶到了鎖
    10-17 19:32:22.422 6459-6523/com.yc.cn.ycbaseadapter I/System.out: Thread-5 已經被鎖定
    10-17 19:32:22.424 6459-6524/com.yc.cn.ycbaseadapter I/System.out: Thread-6 搶到了鎖
    10-17 19:32:22.424 6459-6524/com.yc.cn.ycbaseadapter I/System.out: Thread-6 已經被鎖定
    10-17 19:32:22.427 6459-6525/com.yc.cn.ycbaseadapter I/System.out: Thread-7 搶到了鎖
    10-17 19:32:22.427 6459-6526/com.yc.cn.ycbaseadapter I/System.out: Thread-8 搶到了鎖
    10-17 19:32:22.427 6459-6525/com.yc.cn.ycbaseadapter I/System.out: Thread-7 已經被鎖定
    10-17 19:32:22.427 6459-6526/com.yc.cn.ycbaseadapter I/System.out: Thread-8 已經被鎖定
    10-17 19:32:22.427 6459-6527/com.yc.cn.ycbaseadapter I/System.out: Thread-9 搶到了鎖
    10-17 19:32:22.427 6459-6527/com.yc.cn.ycbaseadapter I/System.out: Thread-9 已經被鎖定
    10-17 19:32:22.428 6459-6528/com.yc.cn.ycbaseadapter I/System.out: Thread-10 搶到了鎖
    10-17 19:32:22.428 6459-6528/com.yc.cn.ycbaseadapter I/System.out: Thread-10 已經被鎖定
    10-17 19:32:22.429 6459-6529/com.yc.cn.ycbaseadapter I/System.out: Thread-11 搶到了鎖
    10-17 19:32:22.429 6459-6529/com.yc.cn.ycbaseadapter I/System.out: Thread-11 已經被鎖定
    10-17 19:32:22.430 6459-6530/com.yc.cn.ycbaseadapter I/System.out: Thread-12 搶到了鎖
    10-17 19:32:22.430 6459-6530/com.yc.cn.ycbaseadapter I/System.out: Thread-12 已經被鎖定
    10-17 19:32:22.431 6459-6532/com.yc.cn.ycbaseadapter I/System.out: Thread-14 搶到了鎖
    10-17 19:32:22.431 6459-6532/com.yc.cn.ycbaseadapter I/System.out: Thread-14 已經被鎖定
    10-17 19:32:22.432 6459-6531/com.yc.cn.ycbaseadapter I/System.out: Thread-13 搶到了鎖
    10-17 19:32:22.433 6459-6531/com.yc.cn.ycbaseadapter I/System.out: Thread-13 已經被鎖定
    
    
    //當ReentrantLock設定false,也就是非公平鎖時
    10-17 19:34:58.102 7089-7183/com.yc.cn.ycbaseadapter I/System.out: Thread-5 搶到了鎖
    10-17 19:34:58.102 7089-7184/com.yc.cn.ycbaseadapter I/System.out: Thread-6 搶到了鎖
    10-17 19:34:58.103 7089-7183/com.yc.cn.ycbaseadapter I/System.out: Thread-5 已經被鎖定
    10-17 19:34:58.103 7089-7185/com.yc.cn.ycbaseadapter I/System.out: Thread-7 搶到了鎖
    10-17 19:34:58.103 7089-7185/com.yc.cn.ycbaseadapter I/System.out: Thread-7 已經被鎖定
    10-17 19:34:58.103 7089-7184/com.yc.cn.ycbaseadapter I/System.out: Thread-6 已經被鎖定
    10-17 19:34:58.104 7089-7186/com.yc.cn.ycbaseadapter I/System.out: Thread-8 搶到了鎖
    10-17 19:34:58.105 7089-7186/com.yc.cn.ycbaseadapter I/System.out: Thread-8 已經被鎖定
    10-17 19:34:58.108 7089-7187/com.yc.cn.ycbaseadapter I/System.out: Thread-9 搶到了鎖
    10-17 19:34:58.108 7089-7187/com.yc.cn.ycbaseadapter I/System.out: Thread-9 已經被鎖定
    10-17 19:34:58.111 7089-7188/com.yc.cn.ycbaseadapter I/System.out: Thread-10 搶到了鎖
    10-17 19:34:58.112 7089-7188/com.yc.cn.ycbaseadapter I/System.out: Thread-10 已經被鎖定
    10-17 19:34:58.112 7089-7189/com.yc.cn.ycbaseadapter I/System.out: Thread-11 搶到了鎖
    10-17 19:34:58.113 7089-7189/com.yc.cn.ycbaseadapter I/System.out: Thread-11 已經被鎖定
    10-17 19:34:58.113 7089-7193/com.yc.cn.ycbaseadapter I/System.out: Thread-14 搶到了鎖
    10-17 19:34:58.113 7089-7193/com.yc.cn.ycbaseadapter I/System.out: Thread-14 已經被鎖定
    10-17 19:34:58.115 7089-7190/com.yc.cn.ycbaseadapter I/System.out: Thread-12 搶到了鎖
    10-17 19:34:58.115 7089-7190/com.yc.cn.ycbaseadapter I/System.out: Thread-12 已經被鎖定
    10-17 19:34:58.116 7089-7191/com.yc.cn.ycbaseadapter I/System.out: Thread-13 搶到了鎖
    10-17 19:34:58.116 7089-7191/com.yc.cn.ycbaseadapter I/System.out: Thread-13 已經被鎖定
    複製程式碼

5.問答測試題

5.1 ReentrantLock和synchronized使用分析

  • ReentrantLock是Lock的實現類,是一個互斥的同步器,在多執行緒高競爭條件下,ReentrantLock比synchronized有更加優異的效能表現。
  • 1 用法比較
    • Lock使用起來比較靈活,但是必須有釋放鎖的配合動作
    • Lock必須手動獲取與釋放鎖,而synchronized不需要手動釋放和開啟鎖
    • Lock只適用於程式碼塊鎖,而synchronized可用於修飾方法、程式碼塊等
  • 2 特性比較
    • ReentrantLock的優勢體現在:
      • 具備嘗試非阻塞地獲取鎖的特性:當前執行緒嘗試獲取鎖,如果這一時刻鎖沒有被其他執行緒獲取到,則成功獲取並持有鎖
      • 能被中斷地獲取鎖的特性:與synchronized不同,獲取到鎖的執行緒能夠響應中斷,當獲取到鎖的執行緒被中斷時,中斷異常將會被丟擲,同時鎖會被釋放
      • 超時獲取鎖的特性:在指定的時間範圍內獲取鎖;如果截止時間到了仍然無法獲取鎖,則返回
  • 3 注意事項
    • 在使用ReentrantLock類的時,一定要注意三點:
      • 在finally中釋放鎖,目的是保證在獲取鎖之後,最終能夠被釋放
      • 不要將獲取鎖的過程寫在try塊內,因為如果在獲取鎖時發生了異常,異常丟擲的同時,也會導致鎖無故被釋放。
      • ReentrantLock提供了一個newCondition的方法,以便使用者在同一鎖的情況下可以根據不同的情況執行等待或喚醒的動作。

關於其他內容介紹

01.關於部落格彙總連結

02.關於我的部落格

相關文章