前面篇幅講了很多理論及原理性東西,今天想了想,來點現實場景的東西把前面的內容串一串
一. 死鎖產生的原因
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); }