java線程學習_02

線程同步

線程安全問題(銀行取錢案例)

銀行取錢的基本流程基本上可以分爲以下步驟

  1. 用戶輸入賬戶,密碼,判斷用戶賬戶,密碼是否匹配(此處省略這一步)
  2. 用戶輸入取款金額
  3. 系統判斷賬戶餘額是否大於取款金額
  4. 如果餘額大於存款金額,則取款成功;如果餘額小於存款金額,則取款失敗
package thread;

/**
 * @author Mike
 * @use 賬戶類
 */
public class Account {
    private String accountNo;
    private double balance;

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(){}
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }
    public int hashCode(){
        return accountNo.hashCode();
    }
    public boolean equals(Object object){
        if(this == object){
            return true;
        }
        if (object!=null && object.getClass() == Account.class){
            Account target = (Account)object;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
}

package thread;

/**
 * @author Mike
 * @use 提供一個取錢的線程類,該線程類根據執行賬戶、取錢數量進行取錢操作
 * ,取錢的邏輯是當餘額不足時,提示餘額不足,當餘額大於取的錢數時,餘額減少
 */
public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    //當多個線程修改同一個共享數據時,將涉及數據安全問題
    public void run(){
        //synchronized (account){
            if (account.getBalance() >= drawAmount){
                System.out.println(getName()+"取錢成功,取出"+drawAmount);
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t餘額爲:"+account.getBalance());
            }else {
                System.out.println(getName() + "取錢失敗!餘額不足!");
            }
				//註釋內容爲同步代碼塊
      //  }
    }
}

測試類:

package thread;

/**
 * @author Mike
 * @use 銀行取錢測試類
 */
public class DrawTest {
    public static void main(String[] args) {
        Account account = new Account("1234567" , 1000);
        new DrawThread("甲" ,account ,800).start();
        new DrawThread("乙",account,600).start();
    }
}

輸出結果
加入兩個人同時從這個賬戶裏取錢,可能會出現餘額小於0的情況(可以這樣理解:甲取出錢,在餘額還沒有來得及更新的時候,乙也來取錢了),顯然這樣的情況我們不想讓它發生,所以我們用同步代碼塊來解決共享數據的安全性問題

同步代碼塊

synchronized(obj){
...
//此處的代碼就是同步代碼塊
}

對Account類進行修改

把18行註釋打開就好了,當然別忘了打開尾括號的註釋

同步鎖lock()

比較常用的是ReentrantLock(可重入鎖)(反正書上說這個常用,我還沒做過關於線程的項目,也不清楚)
使用時的代碼格式如下

class X{
	//定義鎖對象
	private final ReentrantLock lock = new ReentrantLock();
	//定義需要保證線程安全的方法
	public void m(){
	//加鎖
	lock.lock();
	try{
	//需要保證線程安全的代碼
		}
		//使用finally塊來保證釋放鎖
		finally{
		lock.unlock();
		}
	}
}

下面是銀行問題用lock改寫的代碼
Account類

package thread._16_6;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Mike
 * @use 使用ReentrantLock重新完成鎖內容
 */
public class Account {
    //定義鎖對象
    private final ReentrantLock lock = new ReentrantLock();
    private String accountNo;
    private double balance;
    public Account(){}
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }
    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }
    public double getBalance() {
        return this.balance;
    }
    //提供一個線程安全的draw()方法來完成取錢操作
    public void draw(double drawAmount){
        //加鎖
        lock.lock();
        try{
            //賬戶餘額大於取錢數目
            if (balance>=drawAmount){
                System.out.println(Thread.currentThread().getName()+"取錢成功!吐出鈔票:"+drawAmount);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                balance -=drawAmount;
                System.out.println("餘額爲:"+balance);
            }else{
                System.out.println(Thread.currentThread().getName()+"取錢失敗,餘額不足");
            }

        }finally {
            //修改完成,釋放鎖
            lock.unlock();
        }
    }
}

線程類

package thread._16_6;


/**
 * @author Mike
 * @use 提供一個取錢的線程類,該線程類根據執行賬戶、取錢數量進行取錢操作
 * ,取錢的邏輯是當餘額不足時,提示餘額不足,當餘額大於取的錢數時,餘額減少
 */
public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;
    public DrawThread(String name, Account account, double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    //當多個線程修改同一個共享數據時,將涉及數據安全問題

    @Override
    public void run() {
        account.draw(this.drawAmount);
    }
}

測試類

package thread._16_6;



/**
 * @author Mike
 * @use 銀行取錢測試類
 */
public class DrawTest {
    public static void main(String[] args) {
        Account account = new Account("1234567" , 1000);
        new DrawThread("甲" ,account ,800).start();
        new DrawThread("乙",account,600).start();
    }
}

其實到這裏我們又發現了一個問題
同步代碼塊相當於是廁所(惡臭的好例子),一個人進去,另外一個人只有等他出來才能進去。
假如有這樣一種情況,手紙是另一個同步代碼塊,甲拿了紙,想進廁所,乙在廁所,沒帶紙想用紙。哦吼,這就是個死鎖了。關於死鎖的問題下一次複習。

發佈了13 篇原創文章 · 獲贊 10 · 訪問量 621
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章