Java原始碼剖析34講學習筆記~6
阿新 • • 發佈:2020-07-16
目錄
談談你對鎖的理解,如何手動模擬一個死鎖
死鎖
指兩個執行緒同時佔用兩個資源又在彼此等待對方釋放鎖資源
演示程式碼
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 讀寫鎖允許同一時間內有多個執行緒進行讀操作, 屬於共享鎖, 可以理解為樂觀鎖
-