知識要分享,好的東西更要分享出來!
昨天了解了多執行緒死鎖相關知識,以前總聽別人講: 面試的時候可能會問你
1、死鎖產生的條件是什么?
資源的競爭
2、能集體說下那么多執行緒中,產生死鎖的幾個必要條件是什么?
需要同時滿足四個條件
a、互斥,共享資源小x和小y只能被一個執行緒占用
b、持有等待,執行緒X持有資源小x,去等待資源小y的時候不釋放小x
c、不可強占,執行緒X持有資源小x,那么其他執行緒不可強占小x
d、回圈等待, 執行緒X等待執行緒Y占有的資源,執行緒Y等待執行緒X占有的資源
3、那么死鎖問題能被解決么?如何解決?能簡單說說么?
可以被解決
其中:
a:沒有辦法破壞,因為在單執行緒中是不存在死鎖的問題的,那么死鎖
必然產生多執行緒當中,多執行緒那么必定存在共享資源的互斥,因此a無法被破
壞
b:這個競爭條件是可以破壞的,我們可以使競爭的資源被一個執行緒所持有
即可
c:這個競爭條件也是可以破壞的,可以采用ReenTrantLock
d:這個條件也是可以破壞的,我們可以采用順序加鎖的方式去弄
下面就說說具體的案例吧,我們就以轉賬為例:
賬戶類:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author xxx xshlxx@126.com
* @since 2020/12/21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private String accountName;
private int price;
/**
* 當前賬戶轉賬之后剩余金額
*
* @param amount 轉賬金額
*/
public void remainPrice(int amount) {
this.price = this.price - amount;
}
/**
* 轉賬之后的金額
*
* @param amount 轉賬金額
*/
public void transferPrice(int amount) {
this.price = this.price + amount;
}
}
模擬死鎖:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author xxx xshlxx@126.com
* @since 2020/12/21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransferAccount implements Runnable {
private Account fromAccount;
private Account toAccount;
private int amount;
@Override
public void run() {
while (true) {
synchronized (fromAccount) {
synchronized (toAccount) {
fromAccount.remainPrice(amount);
toAccount.transferPrice(amount);
}
}
System.out.println(fromAccount.getAccountName() + "--->" + fromAccount.getPrice());
System.out.println(toAccount.getAccountName() + "--->" + toAccount.getPrice());
}
}
public static void main(String[] args) {
Account fromAccount = new Account("小李",100000);
Account toAccount = new Account("小白",300000);
TransferAccount from = new TransferAccount(fromAccount,toAccount,10);
TransferAccount to = new TransferAccount(toAccount,fromAccount,30);
Thread t1 = new Thread(from);
Thread t2 = new Thread(to);
t1.start();
t2.start();
}
}
運行結果如下:

說一下為什么會產生死鎖?
原因在于如下:
a、存在多個執行緒(t1、t2)
b、從案例中我們可以看出,兩個執行緒的操作為:
b1: 小李向小白轉了10塊錢,小白感覺很開心,覺得要
有所回饋就向小李轉了30!
b2: 所操作的物件: 賬戶小李和賬戶小白,這也是我們執行緒要完成轉賬
操作所需要的共享資源,既然都說開了,那就再說一點,我們以此
案例進行剖析,剖析上面提及到死鎖發生的必須同時滿足的四個條
件
1、資源互斥: 在這里闡述的比較清楚了,假設這里面資源不互斥,那么
就是資源為單執行緒,那么就不存在死鎖,換到這個案例中就是,單向
轉賬,或者說一個執行緒執行完之后另外一個執行緒才能夠執行
2、持有等待,執行緒X持有資源小x時,去等待資源小y而不是釋放小x的資源
換到我們這個案例對應的就是: fromAccount和toAccount兩個賬戶被
不同的執行緒執行緒所持有造成的死鎖,在這里要提及一句: 認真看下兩個
執行緒的上鎖,是存在順序的
3、不可強占,執行緒X持有資源小x時,其他執行緒不可獲取小x的資源,這個也
比較容易,我們反向去思考,如果說,已經持有資源的執行緒資源可以被其
它執行緒強占,那么就不會存在死鎖問題了,因為不存在等待!
4、回圈等待,回圈等待, 執行緒X等待執行緒Y占有的資源,執行緒Y等待執行緒X占有
的資源,在我們這里就是因為一個完成操作需要fromAccount和
toAccount兩個賬戶,有其中一個就會陷入無限等待的可能
上面說了這么多,那么就說下如何解決死鎖的問題吧
···
1: 破壞持有等待不釋放的問題,我們完全可以寫一個"調控器",讓它起到一
個中介的作用,共享資源由它去統一調控
資源調配類:
import java.util.ArrayList;
import java.util.List;
/**
* @author xxx xshlxx@126.com
* @since 2020/12/21
*/
public class Allocator {
private List<Account> accounts = new ArrayList<>();
/**
* 資源統一調配中心
*
* @param fromAccount 轉賬賬戶
* @param toAccount 轉入賬戶
*/
public boolean accountObtainIs(Account fromAccount,Account toAccount) {
if (accounts.contains(fromAccount) || accounts.contains(toAccount)) {
return false;
}
accounts.add(fromAccount);
accounts.add(toAccount);
return true;
}
/**
* 資源釋放
*
* @param fromAccount 轉賬賬戶
* @param toAccount 轉入賬戶
*/
public void free(Account fromAccount,Account toAccount) {
accounts.remove(fromAccount);
accounts.remove(toAccount);
}
}
破壞死鎖產生的條件: 2
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author xxx xshlxx@126.com
* @since 2020/12/21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransferAccountSave2 implements Runnable {
private Account fromAccount;
private Account toAccount;
private int amount;
Allocator allocator;
@Override
public void run() {
try {
if (allocator.accountObtainIs(fromAccount, toAccount)) {
while (true) {
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getPrice() > amount) {
fromAccount.remainPrice(amount);
toAccount.transferPrice(amount);
}
}
}
System.out.println(fromAccount.getAccountName() + "--->" + fromAccount.getPrice());
System.out.println(toAccount.getAccountName() + "--->" + toAccount.getPrice());
}
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
allocator.free(fromAccount, toAccount);
}
}
public static void main(String[] args) {
Account fromAccount = new Account("小李", 100000);
Account toAccount = new Account("小白", 300000);
Allocator allocator = new Allocator();
TransferAccountSave2 from = new TransferAccountSave2(fromAccount, toAccount, 10, allocator);
TransferAccountSave2 to = new TransferAccountSave2(toAccount, fromAccount, 30, allocator);
Thread t1 = new Thread(from);
Thread t2 = new Thread(to);
t1.start();
t2.start();
}
}
運行結果如下:

說明: 其實仔細理解不難發現,這個東西吧,有點意思,可以從兩方面去衡量:
1、是不是相當于把鎖的范圍擴大了?以前鎖相當于鎖當前的賬戶,小李或者
小白,現在相當于同時上鎖,一起獲取才能夠執行,蠻有趣的,
2、對比下rua運算式,redis里面那個(這個我也不大熟),原理是保證原子性
操作,是不是和這個差不多?
···
2: 破壞不可強占的問題,死鎖在這里的原因是因為sychronized把共享資
源給上了鎖,導致其他執行緒無法獲取上鎖的資源,下面我們把這個條件
給破壞掉,采取疑問式寫法
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author xxx xshlxx@126.com
* @since 2020/12/21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransferAccountSave3 implements Runnable {
private Account fromAccount;
private Account toAccount;
private int amount;
private Lock fromLock = new ReentrantLock();
private Lock toLock = new ReentrantLock();
@Override
public void run() {
while (true) {
/**
* 這里面我們采用 ReentrantLock.tryLock()
* 意味: 獲取到鎖回傳true, 獲取不到回傳false
*/
if (fromLock.tryLock()) {
if (toLock.tryLock()) {
if (fromAccount.getPrice() >= amount) {
fromAccount.remainPrice(amount);
toAccount.transferPrice(amount);
}
}
}
System.out.println(fromAccount.getAccountName() + "--->" + fromAccount.getPrice());
System.out.println(toAccount.getAccountName() + "--->" + toAccount.getPrice());
}
}
public TransferAccountSave3(Account fromAccount, Account toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
public static void main(String[] args) {
Account fromAccount = new Account("小李", 100000);
Account toAccount = new Account("小白", 300000);
TransferAccountSave3 from = new TransferAccountSave3(fromAccount, toAccount, 10);
TransferAccountSave3 to = new TransferAccountSave3(toAccount, fromAccount, 30);
Thread t1 = new Thread(from);
Thread t2 = new Thread(to);
t1.start();
t2.start();
}
}
運行結果如下:

···
說明: 不采用sychronized這個關鍵字去加鎖,而是采用ReentrantL
ock.tryLock()去嘗試獲取到,如果獲取到了那么回傳true,如果
為獲取到回傳false,看下這句話:
Acquires the lock only if it is not held by anoth
er thread at the time of invocation.
很清晰了,判斷的是,在同一時間,當前這把鎖是否被其他執行緒所
持有,ok繼續下一個
···
3: 破壞回圈等待問題, 我們可以想辦法保證加鎖的順序,那樣就可以解決掉
回圈等待的問題,如下代碼:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author xxx xshlxx@126.com
* @since 2020/12/21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransferAccountSave4 implements Runnable {
private Account fromAccount;
private Account toAccount;
private int amount;
@Override
public void run() {
Account left = null;
Account right = null;
if (fromAccount.hashCode() > toAccount.hashCode()) {
left = toAccount;
right = fromAccount;
}
while (true) {
synchronized (left) {
synchronized (right) {
if (fromAccount.getPrice() > amount) {
fromAccount.remainPrice(amount);
toAccount.transferPrice(amount);
}
}
}
System.out.println(fromAccount.getAccountName() + "--->" + fromAccount.getPrice());
System.out.println(toAccount.getAccountName() + "--->" + toAccount.getPrice());
}
}
public static void main(String[] args) {
Account fromAccount = new Account("小李", 100000);
Account toAccount = new Account("小白", 300000);
TransferAccountSave4 from = new TransferAccountSave4(fromAccount, toAccount, 10);
TransferAccountSave4 to = new TransferAccountSave4(toAccount, fromAccount, 30);
Thread t1 = new Thread(from);
Thread t2 = new Thread(to);
t1.start();
t2.start();
}
}
···
運行結果如下:

···
分析: 解決了多執行緒中回圈等待的問題,采取的方法是嚴格控制加鎖的順序
,至于為什么?留作評論區
gitlab代碼地址如下: https://github.com/584918038/design-model.git

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/239175.html
標籤:其他
