互斥鎖(上):解決原子性問題
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。