樂觀鎖與悲觀鎖總結
851
2025-04-01
前言
上一篇我們介紹Java內存模型來處理有序性,可見性的問題。但是,還有一個原子性的問題,沒有處理,那么針對原子性的問題我們該怎么處理呢?我們知道在并發編程中的原子性問題主要原因就是,一條高級語句可能會被分成多個CPU指令,在指令執行完之后發生了線程切換,中間狀態被暴露造成原子性問題。
鎖
現實生活中,我們用自己的鎖來保護自己的財產,買門票來鎖定演唱會的座位。
同理,在并發編程的世界里我們同樣可以引入鎖的概念來鎖住需要保護的資源。只有獲得了鎖的線程才能操作資源。
synchronized
Java自帶的鎖工具是synchronized,用synchronized修飾的代碼就相當于上了鎖。上了鎖就需要互斥執行。即:同一時刻只能有一個線程執行。
我們將一段需要互斥執行的代碼稱之為臨界區。
例如:
synchronized (this) { this.a = 100; System.out.println("*******test2執行"); }
1
2
3
4
如上程序:代碼System.out.println("*******test2執行");被synchronized修飾,故此代碼被稱之為臨界區。而a這是受保護的資源。其關系圖如下:
synchronized的運用
synchronized 可以修改方法,修飾代碼塊。使用如下:
class SynchronizedTest { public synchronized void test1() { System.out.println("*******test1執行"); } public void test2() { synchronized (this) { System.out.println("*******test2執行"); } } public synchronized static void test3() { System.out.println("*******test3執行"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
當synchronized修飾實例方法時,鎖定的就是當前實例對象this。如方法test1所示。
當synchronized修飾代碼塊塊時,鎖定的就是括號里的對象。如方法test2所示。
當synchronized修飾靜態方法時,鎖定的就是當前類的class對象,如方法test3所示。
鎖與受保護資源的關系
在現實生活中,我們可以通過通過一把鎖保護多個東西,例如,用一把大門的鎖,保護你家里面的所有東西。同樣的,你一個給一個東西加上兩把鎖。但是,在并發編程中,同一個資源只能由一把鎖保護,一把鎖可以保護多個資源。故,并發編程中,鎖與受保護資源的關系是1:N。例如:
public class SynchronizedTest3 { int a = 0; int b = 0; static int c = 0; /** * 鎖定的是this對象,保護了,a,b兩個資源 */ synchronized void setValue() { a = 100; b = 20; } /** * 鎖定的是SynchronizedTest3的class對象, * 保護了資源c * @return */ synchronized static int getValue() { return SynchronizedTest3.c; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
如上程序,synchronized修飾的setValue方法中有a,b兩個資源,因為這兩個資源都所屬與this對象,所以都可以受到synchronized的保護。
而synchronized修飾的getValue方法中只有資源c,而這個c是一個靜態變量,屬于SynchronizedTest3類,所以它也可以受到保護。
鎖如何保護多個資源
多個資源沒關聯
如果多個資源沒有關聯的話,我們可以用多個不同的鎖來保護,例如:張三的東西用張三的鎖,李四的東西用李四的鎖。井水不犯河水。例如:
public class Account { //取款保護鎖 private final Object balLock = new Object(); //密碼保護鎖 private final Object pwdLock = new Object(); private Integer balance = 100; private String password = null; /** * 取款 */ public void withdrow(Integer amt) { synchronized (balLock) { if (balance > amt) { balance -= amt; System.out.println("*******扣除后的余額是="+balance); } } } /** * 查看余額 * @return */ public int getSBalance() { synchronized (balLock) { System.out.println("******讀取到的余額是="+balance); return balance; } } /** * 更改密碼 * @param newPwd */ public void updatePwd(String newPwd) { synchronized (pwdLock) { password = newPwd; } } /** * 查看密碼 * @return */ public String getNewPwd() { synchronized (pwdLock) { return password; } } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
如上程序,Account類下面有余額和密碼兩個資源,我們分別創建了取款的保護鎖balLock和密碼修改的保護鎖pwdLock。同時我們可以需要注意的是
相同的資源要用相同的鎖,例如取款用的balLock鎖,那么查看余額也需要用balLock鎖。修改密碼用的pwdLock鎖,那么查看密碼也需要用pwdLock鎖。只要這樣才能保證數據的正確性。在本例中,我們也可以用this鎖來保護,但是這樣的話,修改密碼和取款就不能分開。用兩個不同的鎖,可以使得取款和修改密碼可以并行。
2. 多個資源有關聯
現實中,有很多資源是有關聯關系的,例如:轉賬操作:張三給李四轉賬100元,那么張三的賬戶余額和李四的賬戶余額就是有關聯關系的兩個資源。那么:我們該如何加鎖保護這兩個資源使得轉賬沒問題呢?
我們先直接用synchronized修飾轉賬方法 如下:
synchronized void transfer(Account target, int amt) { if (balance > amt) { this.balance = balance - amt; target.setBalance(target.getBalance() + amt); } }
1
2
3
4
5
6
通過前面的描述,我們知道synchronized鎖定的對象是this,那么該鎖肯定能保護資源this.balance。但是對于資源target.balance卻不能保護,因為該資源屬于target對象。
故此種方法不可行。
我們來具體分析下:
假如有賬戶A(有100元), 賬戶B(有100元),賬戶C(有100元),三個賬戶,賬戶A給賬戶B轉50元,賬戶B給賬戶C轉50元。所以我們期望的結果是賬戶A 剩下50元,賬戶B剩下100元,賬戶C剩下150元。但是按照我們上述加鎖方式,一定會是在這樣么?
我們假設線程1執行賬戶A轉賬戶B50元,線程2執行賬戶B給賬戶C50元,那么這兩個線程運行在CPU1和CPU2,顯然CPU1和CPU2是不互斥的。
線程1獲取到的鎖是A.this。線程2獲取到鎖是B.this。如果線程1和線程2同時進入臨界區,那么讀取到的賬戶B余額都是100元,如果線程1先于線程2執行完,那么賬戶B的余額是50。如果線程2先于線程1執行完那么賬戶B的余額是150元。就是不可能是100元。
上面的原因就是因為鎖沒有覆蓋到所有的應受保護資源。那么該如何處理呢?我們可以通過一個共享對象來處理保護資源。 例如:Accout.class對象,這個對象是所有的余額所共享的,所以能夠覆蓋this.balance資源和
target.balance資源。
void transfer3(Account target, int amt) { synchronized (Account.class) { if (balance > amt) { this.balance = balance - amt; target.setBalance(target.getBalance() + amt); } } }
1
2
3
4
5
6
7
8
9
不過在實際項目中,我們都是通過數據庫事務+數據庫樂觀鎖來處理轉賬邏輯的。
總結
本文簡單的介紹了鎖模型以及synchronized的用法和,鎖與資源的關系,最后介紹了鎖如何保護多個資源,總結一下就是 鎖與資源的關系是 1:N,多個沒有關聯的資源用多個不同的鎖進行保護,有關聯關系的資源,用共享鎖進行保護。
說明
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。