1. 程式人生 > 其它 >互斥鎖(上):解決原子性問題

互斥鎖(上):解決原子性問題

1)回顧一下什麼是原子性?

  • 一個或多個操作在CPU執行的過程中不被中斷的特性,稱為原子性。

2)原子性問題的源頭是什麼?

  • 執行緒切換

3)原子性問題到底該如何解決呢?

  • 既然原子性問題的產生源頭是執行緒切換,而執行緒切換依賴CPU中斷的,所以禁止CPU發生中斷就能禁止執行緒切換

4)為什麼說單核的時候好解決原子性,而多核不好解決?

  • 人多了不好管理

5)32 位 CPU 上執行 long 型變數的寫操作是怎樣的步驟?

  • 被分成了兩次寫操作(寫高 32 位和寫低 32 位)

6)互斥怎麼理解?

  • 同一時刻只有一個執行緒執行

7)保證原子性的關鍵是什麼?

  • 保證對共享變數的修改是互斥的

8)互斥的最簡單方案就是加鎖,那麼簡易鎖模型是怎樣的?

9)簡易鎖模型有哪些不足

  • 我們不能清晰的知道鎖的是什麼?我們保護的又是什麼?

10)從哪些方面去改進我們的簡易鎖模型?

  • 在現實世界裡,鎖和鎖要保護的資源是有對應關係的,比如你用你家的鎖保護你家的東西,我用我家的鎖保護我家的東西。

  • 在併發程式設計世界裡,鎖和資源也應該有這個關係。

11)改進後的鎖模型應該是怎樣的?

12)改進後的鎖有哪些注意要點?

  • 鎖 LR 和受保護資源之間,我特地用一條線做了關聯,這個關聯關係非常重要。很多併發 Bug 的出現都是因為把它忽略了,然後就出現了類似鎖自家門來保護他家資產的事情

13)Java 語言提供的鎖技術是什麼?

  • synchronized

14)synchronized可以鎖哪些物件?

  • 方法

  • 程式碼塊

15)寫一段程式碼看看?


class X {
// 修飾非靜態方法
synchronized void foo() {
// 臨界區
}
// 修飾靜態方法
synchronized static void bar() {
// 臨界區
}
// 修飾程式碼塊
Object obj = new Object()
void baz() {
synchronized(obj) {
// 臨界區
}
}
}

16) 為什麼程式碼中沒有看見加鎖---解鎖這個順序?,不符合上面的模型啊!

  • java悄悄滴幫我們加上了。

17)java自動幫我們加鎖解鎖對我們有什麼好處?

  • 加鎖 lock() 和解鎖 unlock() 一定是成對出現的,避免我們忘記解鎖導致bug

18)上面的程式碼中synchronized在程式碼塊中鎖的物件是obj。那它在修飾方法的時候鎖的物件是什麼?

  • 修飾靜態方法:鎖的是當前類的 Class 物件

    // 上面的例子相當於
    class X {
    // 修飾靜態方法
    synchronized(X.class) static void bar() {
    // 臨界區
    }
    }

  • 修飾非靜態方法:鎖的是當前例項物件 this


    class X {
    // 修飾非靜態方法
    synchronized(this) void foo() {
    // 臨界區
    }
    }

19)怎樣用synchronized 來解決 count+=1 問題?

class SafeCalc {
long value = 0L;
long get() {
return value;
}
synchronized void addOne() {
value += 1;
}
}

20)上面的程式碼真的解決了併發問題嗎?

  • addOne() 方法,被 synchronized 修飾後。無論是單核 CPU 還是多核 CPU,只有一個執行緒能夠執行 addOne() 方法,所以一定能保證原子操作

20.1)雖然synchronized解決了addOne() 原子性問題,那是否有可見性問題呢?

  • 前一個執行緒在臨界區修改的共享變數(該操作在解鎖之前),對後續進入臨界區(該操作在加鎖之後)的執行緒是可見的。

  • 我們上面說的前後執行緒可見針對的物件是addOne() 方法。

現在我要求的是get() 方法去讀addOne() 方法,他兩沒啥關係,所以get()方法是看不見我們addOne()方法的變化的

20.2)如何解決get()方法看不見addOne()方法的問題呢?

  • get() 方法也 synchronized 一下


    class SafeCalc {
    long value = 0L;
    synchronized long get() {
    return value;
    }
    synchronized void addOne() {
    value += 1;
    }
    }
  • 他兩鎖的物件都是this,所以根據管程中的幾大規則,就保證了我們get能夠看見addOne的變化。

20.3)上面程式碼鎖模型圖是怎麼樣的?

你去看球賽,一張票一個座位。座位就是受保護資源,門票就是鎖,檢票人員就是synchronized。

21)鎖和受保護資源的關係是什麼?

  • 一把鎖可以鎖多個資源,一個資源只能有一把鎖。

  • 一張包場票包下所有座位,一個座位只能賣一張票,否則打架。

22)分析下面改動的程式碼是否存在併發問題?


class SafeCalc {
static long value = 0L;
synchronized long get() {
return value;
}
synchronized static void addOne() {
value += 1;
}
}

靜態方法的鎖是類,而普通方法的鎖是this物件,這是兩把不同的鎖,所以存在併發問題。

23)下面的程式碼用 synchronized 修飾程式碼塊來嘗試解決併發問題,你覺得這個使用方式正確嗎?有哪些問題呢?能解決可見性和原子性問題嗎?


class SafeCalc {
long value = 0L;
long get() {
synchronized (new Object()) {
return value;
}
}
void addOne() {
synchronized (new Object()) {
value += 1;
}
}
}
  • 1)兩個程式碼塊鎖物件是不同的物件。

  • 2)經過JVM逃逸分析的優化後,這個sync程式碼直接會被優化掉,所以在執行時該程式碼塊是無鎖的

24)加鎖的本質是什麼?

  • 在鎖物件的物件頭中寫入當前執行緒id。