學習筆記【多執行緒-第四節:synchronized的淺顯基礎及安全問題】
技術標籤:筆記多執行緒java併發程式設計thread程式語言
執行緒同步
執行緒非同步模型
各執行緒自己執行自己的,互相沒有什麼關聯,比如有兩個執行緒,一個是弟弟在吃飯,一個是姐姐在看電視,誰也沒有影響到誰。
執行緒同步模型
在我理解,就是執行緒之間存在著某種等待關係,一個執行緒執行時必須等另一個行程結束,好比有兩個執行緒,一個共享物件菜,有兩個執行緒,姐姐夾菜和弟弟夾菜,姐姐夾菜時弟弟需要等著,弟弟夾菜時需要等著,這樣的話效率較低,需要排隊執行,其中涉及到了關鍵字synchronized(同步)。
執行緒安全問題
存在安全問題的條件
1.多執行緒併發
2.有共享資料
3.都對共享物件有修改的行為
例子
假設簡單的模擬一個銀行多執行緒取款,有一個Account使用者類,有賬號和餘額兩個例項變數,有一個取款方法,傳入一個取款額的引數,睡一秒(模擬網路延時),然後改賬戶餘額;有一個取款執行緒,有一賬戶例項變數;主方法中,建立兩個取錢執行緒,建立一個賬戶,兩個執行緒分別傳入這個賬戶對其進行取款5000的操作。
Account類:
class Account{//使用者執行緒
private String actNo;
private double balance;
public Account(String actNo, double balance) {
this .actNo = actNo;
this.balance = balance;
}
public String getActNo() {return actNo;}
public void setActNo(String actNo) { this.actNo = actNo;}
public double getBalance() { return balance;}
public void setBalance(double balance) {this.balance = balance;}
@Override
public String toString() {
return "Account{" +
"actNo='" + actNo + '\'' +
'}';
}
public void withDraw(double money){//取款方法
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(getBalance()-money);
}
}
取款執行緒:
class MyWithdrawThread extends Thread{//取錢執行緒
private Account act;
public MyWithdrawThread(Account act) {
this.act = act;
}
@Override
public void run() {
int money=5000;
act.withDraw(money);
System.out.println(Thread.currentThread().getName()+"取款"+money+"元成功!餘額"+ act.getBalance()+"元!");
}
}
主方法:
public class WithdrawThreadTest {
public static void main(String[] args) {
Account account=new Account("act-01",10000);
MyWithdrawThread t1=new MyWithdrawThread(account);
MyWithdrawThread t2=new MyWithdrawThread(account);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
結果:
結果兩個執行緒都對其取款了5000,但餘額還是五千,這是因為兩個執行緒讀到餘額時都讀到了10000,都對其進行了改值並輸出了,如果真的是銀行,就虧的血本無歸了,當然,這只是很簡單的模擬,和大佬比不了…
這就是執行緒安全問題,這時我們可以在取錢方法上加上synchronized來給其加把鎖,讓一個執行緒對共享資料進行更改時另一個執行緒則在外等著。
public void withDraw(double money){
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(getBalance() - money);
}
}
這時,輸出的就是對的了。
在這裡,我用的是this,也就是隻該物件,這不是必須的,要鎖什麼執行緒,把這些執行緒的一個共享物件放上去就可以了。
由於例項變數在堆(heap)中,靜態方法在方法區(method area)中,區域性變數在棧(stack)中,執行緒共享堆記憶體和方法區,而一個執行緒一個棧。所以區域性變數是不會出現執行緒安全問題的。
當然,還可以將synchronized放在例項方法上,那麼預設鎖的就是this,這種方法不夠靈活,以為可能有很多操作並不是修改共享資料,都被鎖住效率較低;synchronized也可以用在靜態方法上,那鎖住的則是一個類,一個類僅此一把鎖。
注意
synchronized最好不要巢狀使用,要不一不小心就會發生死鎖的情況,也不好除錯。