執行緒鎖(四)
前面篇幅講了很多理論及原理性東西,今天想了想,來點現實場景的東西把前面的內容串一串
一. 死鎖產生的原因
1) 系統資源的競爭
通常系統中擁有的不可剝奪資源,其數量不足以滿足多個進程運行的需要,使得進程在 運行過程中,會因爭奪資源而陷入僵局,如磁帶機、印表機等。只有對不可剝奪資源的競爭 才可能產生死鎖,對可剝奪資源的競爭是不會引起死鎖的。
2) 進程推進順序非法
進程在運行過程中,請求和釋放資源的順序不當,也同樣會導致死鎖。例如,並發進程 P1、P2分別保持了資源R1、R2,而進程P1申請資源R2,進程P2申請資源R1時,兩者都 會因為所需資源被佔用而阻塞。
訊號量使用不當也會造成死鎖。進程間彼此相互等待對方發來的消息,結果也會使得這 些進程間無法繼續向前推進。例如,進程A等待進程B發的消息,進程B又在等待進程A 發的消息,可以看出進程A和B不是因為競爭同一資源,而是在等待對方的資源導致死鎖。
3) 死鎖產生的必要條件
產生死鎖必須同時滿足以下四個條件,只要其中任一條件不成立,死鎖就不會發生。
- 互斥,共享資源 X 和 Y 只能被一個執行緒佔用;
- 佔有且等待,執行緒 T1 已經取得共享資源 X,在等待共享資源 Y 的時候,不釋放共享資源 X;
- 不可搶佔,其他執行緒不能強行搶佔執行緒 T1 佔有的資源;
- 循環等待,執行緒 T1 等待執行緒 T2 佔有的資源,執行緒 T2 等待執行緒 T1 佔有的資源,就是循環等待
下面就定義來演示一個死鎖場景的例子:
public class Transfer {
private String accountName;
private int balance;
public Transfer(String accountName, int balance) {
this.accountName = accountName;
this.balance = balance;
}
public void debit(int amount){ //更新轉出方的餘額
this.balance-=amount;
}
public void credit(int amount){ //更新轉入方的餘額
this.balance+=amount;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
public class Deadlock implements Runnable{
private Transfer fromAccount; //轉出賬戶
private Transfer toAccount; //轉入賬戶
private int amount; //轉入金額
public Deadlock(Transfer fromAccount, Transfer toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getBalance() >= amount) {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
//轉出賬戶的餘額
System.out.println(fromAccount.getAccountName() + "->" + fromAccount.getBalance());
//轉入賬戶的餘額
System.out.println(toAccount.getAccountName() + "-->" + toAccount.getBalance());
}
}
public static void main(String[] args) throws InterruptedException {
Transfer fromAccount=new Transfer("用戶A",100000);
Transfer toAccount=new Transfer("用戶B",300000);
Thread a =new Thread(new Deadlock(fromAccount,toAccount,10));
Thread b=new Thread(new Deadlock(toAccount,fromAccount,30));
a.start();
b.start();
}
}
如下圖,死鎖條件產生了,下面死鎖產生的條件就是互斥產生的,想要破壞死鎖只用破壞死鎖的四大條件的其中一個就可以,下面我們就上面程式碼優化下,破壞佔有且等待這個條件來破壞。

我們增加一個類,專門用來分配資源
public class Allocator {
private List<Object> list=new ArrayList<>();
//保證互斥,只有一個執行緒能進入此方法
synchronized boolean apply(Object from,Object to){
if(list.contains(from)||list.contains(to)){
return false;
}
list.add(from);
list.add(to);
return true;
}
synchronized void free(Object from,Object to){
list.remove(from);
list.remove(to);
}
}
修改原有的Deadlock類
public class Deadlock implements Runnable{
private Transfer fromAccount; //轉出賬戶
private Transfer toAccount; //轉入賬戶
private int amount; //轉入金額
Allocator allocator;
public Deadlock(Transfer fromAccount, Transfer toAccount, int amount,Allocator allocator) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
this.allocator=allocator;
}
@Override
public void run() {
while(true){
//鎖力度調大,在if層面
if (allocator.apply(fromAccount,toAccount)) {
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getBalance() >= amount) {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
}
//轉出賬戶的餘額
System.out.println(fromAccount.getAccountName() + "->" + fromAccount.getBalance());
//轉入賬戶的餘額
System.out.println(toAccount.getAccountName() + "-->" + toAccount.getBalance());
}
}
public static void main(String[] args) throws InterruptedException {
Transfer fromAccount=new Transfer("用戶A",100000);
Transfer toAccount=new Transfer("用戶B",300000);
Allocator allocator=new Allocator();
Thread a =new Thread(new Deadlock(fromAccount,toAccount,10,allocator));
Thread b=new Thread(new Deadlock(toAccount,fromAccount,30,allocator));
a.start();
b.start();
}
}
自己運行下會發現死鎖解決了,上面我們是破壞了佔有且等待這個條件解決了死鎖,下面我們就不可搶佔這個條件來解決死鎖問題,我們利用Lock的屬性來破壞,將Deadlock程式碼再修改一次(Lock我想在下個篇幅中具體講)
public class Deadlock1 implements Runnable{
private Transfer fromAccount; //轉出賬戶
private Transfer toAccount; //轉入賬戶
private int amount; //轉入金額
Lock fromLock=new ReentrantLock();
Lock toLock=new ReentrantLock();
public Deadlock1(Transfer fromAccount, Transfer toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
@Override
public void run() {
while(true){
if (fromLock.tryLock()) {
if (toLock.tryLock()) {
if (fromAccount.getBalance() >= amount) {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
//轉出賬戶的餘額
System.out.println(fromAccount.getAccountName() + "->" + fromAccount.getBalance());
//轉入賬戶的餘額
System.out.println(toAccount.getAccountName() + "-->" + toAccount.getBalance());
}
}
public static void main(String[] args) throws InterruptedException {
Transfer fromAccount=new Transfer("用戶A",100000);
Transfer toAccount=new Transfer("用戶B",300000);
Thread a =new Thread(new Deadlock1(fromAccount,toAccount,10));
Thread b=new Thread(new Deadlock1(toAccount,fromAccount,30));
a.start();
b.start();
}
}
最後我們再循環等待條件破壞死鎖
public class Deadlock2 implements Runnable{
private Transfer fromAccount; //轉出賬戶
private Transfer toAccount; //轉入賬戶
private int amount; //轉入金額
public Deadlock2(Transfer fromAccount, Transfer toAccount, int amount ) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
@Override
public void run() {
Transfer left=null;
Transfer right=null;
//控制加鎖順序
if (fromAccount.hashCode()>toAccount.hashCode()){
left=toAccount;
right=fromAccount;
}
while(true){
synchronized (left) {
synchronized (right) {
if (fromAccount.getBalance() >= amount) {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
//轉出賬戶的餘額
System.out.println(fromAccount.getAccountName() + "->" + fromAccount.getBalance());
//轉入賬戶的餘額
System.out.println(toAccount.getAccountName() + "-->" + toAccount.getBalance());
}
}
public static void main(String[] args) throws InterruptedException {
Transfer fromAccount=new Transfer("用戶A",100000);
Transfer toAccount=new Transfer("用戶B",300000);
Allocator allocator=new Allocator();
Thread a =new Thread(new Deadlock(fromAccount,toAccount,10,allocator));
Thread b=new Thread(new Deadlock(toAccount,fromAccount,30,allocator));
a.start();
b.start();
}
}
二.Thread.join
如果我們想要執行緒的結果對主執行緒可見我們可以用join,看下圖結果我們一定有問題,為啥加了join就對主執行緒可見,下面我們就join的原理講解下

我們點擊t.join();看下join方法做了什麼事情,看到下面源碼我們發現一個關健字wait,如果看過我前面寫的文章朋友應該很快想到他的原理就是阻塞,想到了阻塞那就一定有喚醒,那我們想,喚醒阻塞程式碼的地方會在哪裡呢。
//首先源碼中方法中加了鎖
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
一個執行緒在run方法執行結束時就代表一個執行緒的終止,執行緒終止會調用C的javaThread:: exit方法;在這個方法裡面會有個清理的方法叫ensure_join(this);這個方法會喚醒阻塞方法下的執行緒;
三.ThreadLocal
ThreadLocal為解決多執行緒程式的並發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。說白點就是實際上一種執行緒隔離機制,也是為了保證在多執行緒環境下對於共享變數的訪問的安全性。下面我們就一個例子說明下

上面的例子我們發現每個執行緒間存在干擾問題,而且有的執行緒取的值還是相同的,如果我們想做到執行緒問的隔離,那我們就可以用到ThreadLocal,下圖結果我們發現做到執行緒的隔離

下面我們就ThreadLocal是如何實現的,我們進入源碼

在源碼的set方法
public void set(T value) {
//獲取當前執行緒
Thread t = Thread.currentThread();
//有Map最先想到的是存儲,我們點進去看下
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
點進去好像也沒有啥東西
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
//ThreadLocal.ThreadLocalMap threadLocals = null;
}
那我們就行先暫時跳過,我們走if邏輯,我們第一次進來map是空的所有走createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
下面這段程式碼對每個執行緒都會創建一個存儲,這就做到了執行緒間的隔離,而且下面源碼中用到了hash計算及線性探測,如果我要全寫完的話要化更多的時間,而且跟我要講的執行緒主題偏遠了,後面如果有時間,我再單獨就這塊內容的線性探測深入講解下
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//有興趣可以點進看下Entry;這一步是初始化一個長度
table = new Entry[INITIAL_CAPACITY];
//這裡面i的值其實就是一個下標,這裡里其實就是用hash計算出下標位置,然後將數據存儲起來
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

