【多線程】使用notify實現通信

我們知道,線程的執行具有不確定性。因此,當我們需要控制線程的執行邏輯時,可以通過一些機制來保證線程的協調運行。Java提供瞭如下的幾個方法,來支持一些簡單的線程通信,他們都是屬於Object類的方法。

  • wait():導致當前線程等待
  • notify():通知喚醒其他的在此同步監視器上等待的單個線程,具有隨機選擇性
  • notifyAll():通知喚醒其他的再次同步監視器上所有等待的線程

爲了做練習,我們使用最常見的銀行家示例來練習簡單的通信功能,參考《瘋狂Java講義》。

假設當前有一個賬戶,他必須先進行一次存款,才能進行一次取款操作,並且操作只能交替執行,不能重複執行。下面演示了這個例子如何完成。

1.銀行賬戶類

package com.money;
// 該程序控制取錢操作和存錢操作必須交替進行
public class Account {
	private String accountNo;
	
	private double balance;
	
	private boolean flag = false;
	
	public Account() {}
	
	public Account(String accountNo, double balance) {
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	// 取款
	public synchronized void draw(double drawAmount) {
		try {
			// 表明還沒有存錢進來,則阻塞該線程
			if(!flag) {
				wait();
			}else {
				// 執行取錢操作
				System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount);
				balance -= drawAmount;
				// 表明已取錢狀態
				flag = false;
				// 喚醒其他阻塞的線程
				notifyAll();
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	// 存款
	public synchronized void deposit(double depositAmount) {
		try {
			// 表明當前已經存過一次錢了,阻塞
			if(flag) {
				wait();
			}else {
				System.out.println(Thread.currentThread().getName()+ " 存款:" + depositAmount);
				balance += depositAmount;
				// 設置已存款狀態
				flag = true;
				// 喚醒其他線程
				notifyAll();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	// 餘額不允許隨便修改,因此只提供getter方法
	public String getAccountNo() {
		return accountNo;
	}

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

	public double getBalance() {
		return balance;
	}
	
	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
}

2.存錢類(線程)

package com.money;

// 存錢線程
public class DepositThread extends Thread{
	// 模擬用戶賬戶
	private Account account;
	
	// 當前線程所希望存錢的數量
	private double depositAmount;
	
	// 構造函數
	public DepositThread(String name, Account account, double depositAmount) {
		// 線程名稱
		super(name);
		// 賬戶信息
		this.account = account;
		// 取錢金額
		this.depositAmount = depositAmount;
	}
	
	// 重複100次執行存錢操作
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println(getName() + "(" + i + ")");
			this.account.deposit(this.depositAmount);
		}
		System.out.println(getName() + "完成了所有的操作");
	}
}

3.取錢類(線程)

package com.money;

// 取錢線程
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;
	}
	
	// 重複100次執行取錢操作
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println(getName() + "(" + i + ")");
			this.account.draw(drawAmount);
		}
		
		System.out.println(getName() + "完成了所有的操作");
	}
}

4.測試類

package com.money;

public class DrawTest {
	public static void main(String[] args) {
		// 創建一個賬戶
		Account account = new Account("1016037677", 1000);
		// 創建取錢、存錢用戶(線程)
		new DrawThread("取錢者A", account, 50).start();
		new DepositThread("存錢者B", account, 60).start();
		new DepositThread("存錢者C", account, 60).start();
		new DrawThread("取錢者D", account, 60).start();
	}
}

5.輸出結果

注意其中flag旗幟參數,是用於表明當前存取錢狀態的參數,當我們存過一次錢之後將其設置爲true,以保證存錢操作進來時,若發現當前處於存錢狀態,則將線程設置爲等待狀態,只有當進行過一次取錢操作之後,該狀態才重新標記爲false,從而喚醒等待存錢的操作。

另外還要注意一點的是,我們在Account類的存、取錢方法上添加了synchronized關鍵字,用於表明此方法是一個同步方法,同步監視器爲當前對象,從而保證了資源不會被搶佔,這是多線程安全的基礎。

關於線程通信,還有其他的方法實現,例如Condition類等。

共勉。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章