1. 程式人生 > 其它 >學習筆記【多執行緒-第四節:synchronized的淺顯基礎及安全問題】

學習筆記【多執行緒-第四節: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最好不要巢狀使用,要不一不小心就會發生死鎖的情況,也不好除錯。