1. 程式人生 > 實用技巧 >Java原始碼剖析34講學習筆記~6

Java原始碼剖析34講學習筆記~6

目錄

談談你對鎖的理解,如何手動模擬一個死鎖

死鎖

指兩個執行緒同時佔用兩個資源又在彼此等待對方釋放鎖資源

演示程式碼

public class LockExample {
    public static void main (String[] args) {
        deadLock(); // 死鎖
    }
    
    private static void deadLock() {
        Object lock1 = new Object();
        Object lock2 = new Object();
        
        // 執行緒一擁有lock1 試圖獲取lock2
        new Thread(() -> {
            synchronized(lock1) {
                System.out.println("獲取lock1成功");
                try{
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 試圖獲取鎖lock2
                synchronized(lock2) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
        
        // 執行緒二lock2試圖獲取lock1
        new Thread(() -> {
            synchronized(lock2) {
                System.out.println("獲取lock2成功");
                try{
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 試圖獲取鎖lock2
                synchronized(lock1) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
    }
}

鎖相關面試問題

  • 什麼是樂觀鎖和悲觀鎖? 他們的應用都有哪些? 樂觀鎖有什麼問題?

    • 悲觀鎖

      • 指的是資料對外界的修改採取保守策略, 它認為執行緒很容易會把資料修改掉, 因此在整個資料被修改的過程中都會採用鎖定狀態, 直到一個執行緒使用完, 其他執行緒才可以繼續使用

      • 程式碼如下:

        public class LockExample {
            public static void main(String[] args) {
                synchronized(LockExample.class) {
                    System.out.println("lock");
                }
            }
        }
        
      • 反編譯結果如下:

        Complied from "LockExample.java"
        public class com.example.interview.ext.LockExample{
            public com.example.interview.ext.LockExample();
             Code:
              0: aload_0
              1: invokespecial #1	// Method java/lang/Object."<init>";()V
              4: return
            public static void main(java.lang.String[]);
             Code:
              0: ldc	#2	// class com/example/interview/ext/LockExample
              2: dup
              3: astore_1
              4: monitorenter // 加鎖
              5: getstatic	#3	// Field java/lang/System.out:Ljava/io/PrintStream;
              8: ldc	#4	// String lock
              10: invokevirtual	#5	// Method java/io/PrintStream.println:(Ljava/lang/String;)V
              13: aload_1
              14: monitorexit	// 釋放鎖
              15: goto	23
              18: astore_2
              19: aload_1
              20: monitorexit
              21: aload_2
              22: athrow
              23: return
             Exception table:
              from to target type
               5	15	18	any
               18	21	18	any
        }
        
    • 樂觀鎖

      • 樂觀鎖認為一般情況下資料在修改時不會出現衝突, 所以在資料訪問之前不會加鎖, 只是在資料提交更改時, 才會對資料進行檢測, 因此不會造成死鎖

      • Java中的樂觀鎖大部分是通過 CAS (Compare And Swap, 比較並交換) 操作實現

      • CAS 是一個多執行緒同步的原子指令, CAS操作包含三個重要的資訊: 記憶體位置, 預期原值, 新值

      • CAS 衍生問題 ABA的常見處理方式: 新增版本號, 每次修改之後更新版本號

      • JDK在1.5時提供了 AtomicStamedReference 類也可以解決 ABA 的問題, 此類維護了一個"版本號" Stamp, 每次在比較時不止比較當前值還比較版本號

        public class AtomicStampedReference<V> {
            public static class Pair<V> {
                final T reference;
                final int stamp;	// 版本號
                private Pair(T reference, int stamp){
                    this.reference = reference;
                    this.stamp = stamp;
                }
                static <T> Pair<T> of(T reference, int stamp) {
                    return new Pair<T>(reference, stamp);
                }
            }
            // 比較並設定
            public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp){
                Pair<V> current = pair;
                return expectedREference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)));
            }
            // ...其他原始碼
        }
        
  • 什麼是可重入鎖? 用程式碼如何實現? 他的實現原理是什麼?

    • 可重入鎖

      • 也叫遞迴鎖, 指的是同一個執行緒, 如果外面的函式擁有此鎖之後, 內層的函式也可以繼續獲取該鎖, 在 Java 語言中 ReentrantLock 和 synchronized 都是可重入鎖

        /**
         * synchronized  演示可重入鎖
         */
        public class LockExample{
            public static void main(String[] args) {
                reentrantA();	// 可重入鎖
            }
            /**
             * 可重入鎖A方法
             */
            private synchronized static void reentrantA() {
                System.out.println(Thread.currentThread().getName() + "執行 reentrantA");
                reentrantB();
            }
            /**
             * 可重入鎖A方法
             */
            private synchronized static void reentrantB() {
                System.out.println(Thread.currentThread().getName() + "執行 reentrantB");
            }
        }
        
      • 可重入鎖內部維護了一個計數器, 每加一重鎖計數器 +1, 退出則 -1, 當計數器為 0 時, 則表示當前物件未加鎖

  • 什麼是共享鎖和獨佔鎖?

    • 獨佔鎖

      • 只能被單執行緒持有的鎖, 指的是在任何時候, 最多隻能有一個執行緒持有該鎖, ReentrantLock 就是獨佔鎖, 可以理解為悲觀鎖
    • 共享鎖

      • 可以被多執行緒持有的鎖, ReadWriteLock 讀寫鎖允許同一時間內有多個執行緒進行讀操作, 屬於共享鎖, 可以理解為樂觀鎖