1. 程式人生 > >多執行緒(四)執行緒的同步之同步方法

多執行緒(四)執行緒的同步之同步方法

與同步程式碼塊對應的,Java的多執行緒安全支援還提供了同步方法,同步方法就是使用synchronized關鍵字來修飾某個方法,該方法稱為同步方法。對於同步方法而言,無須顯式指定同步監視器,同步方法的同步監視器是this,也就是該物件本身。

通過使用同步方法可以非常方便地將某類程式設計執行緒安全的類,執行緒安全的類具有如下特徵:

A、該類的物件可以被多個執行緒安全的訪問;

B、每個執行緒呼叫該物件的任意方法之後都將得到正確的結果;

C、每個執行緒呼叫該物件的任意方法之後,該物件狀態依然保持合理狀態。

在學習Java基礎的時候,我們接觸過可變類和不可變類,其中不可變類總是執行緒安全的,因為它的物件的狀態不可改變。但可變物件需要額外的方法來保證其執行緒安全。例如上面的Account就是一個可變類,它的account和balance兩個屬性都可變,當兩個執行緒同時修改Account物件的balance屬性時,程式就出現了異常。下面我們將Account類對balance的訪問設定成執行緒安全的,那麼程式只要把修改的balance的方法修改成同步方法即可。

package gblw.first;


public class Account {
	//封裝賬號編碼、賬號餘額兩個屬性
	private String accountNo;
	private double balance;
	
	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;
	}

	//因此賬戶餘額不允許隨便修改,所以取消balance屬性的setter方法。
	public double getBalance() {
		return balance;
	}

	//提供一個執行緒安全draw方法來完成取錢操作
	public synchronized void draw(double drawAmount){
		//賬戶餘額大於取錢數目
		if(balance>=drawAmount){
			//吐出鈔票
			System.out.println(Thread.currentThread().getName()+"取錢成功!吐出鈔票:"+drawAmount);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//修改餘額
			balance-=drawAmount;
			System.out.println("\t餘額為:"+balance);
		}else{
			System.out.println(Thread.currentThread().getName()+"取錢失敗!餘額不足!");
		}
	}
	
	
	//下面兩個方法根據accountNo來計算Account的hashCode和判斷equals
	public int hashCode(){
		return accountNo.hashCode();
	}
	
	public boolean equals(Object obj){
		if(obj!=null&&obj.getClass()==Account.class){
			Account target=(Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
package gblw.first;

import gblw.first.Account;

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() {
		//直接呼叫account物件的draw方法來執行取錢
		account.draw(drawAmount);
	}
	
	
}
上面DrawThread類無須自己實現取錢操作,而是直接呼叫account的draw方法來執行取錢。由於我們已經使用了synchronized關鍵字保證了draw方法的執行緒安全性,所以多執行緒併發呼叫draw方法也不會出現問題。

此時的程式把draw方法定義在Account裡,而不是直接在run方法中實現取錢邏輯,這種做法更符合面向物件規則。在面向物件裡有一種流行的設計方式:Domain Driven Design(即領域驅動設計,簡稱DDD),這種方式認為每個類都應該是完備的領域物件,例如Account它代表使用者賬戶,它應該提供使用者賬戶的相關方法,例如通過draw()方法來執行取錢操作(實際上還應該提供transfer等方法來完成轉賬等操作),而不是直接將setBalance()方法暴露出來任人操作,這樣才可以更好地保證Account物件的完整性和一致性。

可變類的執行緒安全是以降低程式的執行效率作為代價的,為了減少執行緒安全所帶來的負面影響,程式可以採用如下策略:

A、不要對執行緒安全類的所有方法都進行同步,只對那些會改變競爭資源(競爭資源也就是共享資源)的方法進行同步。例如上面的Account類中的accountNo屬性就無須同步,所以程式只對draw方法進行同步控制。

B、如果可變類有兩種執行環境:單執行緒環境和多執行緒環境,則應該為該可變類提供兩種版本:執行緒不安全版本和執行緒安全版本。在單執行緒環境中使用執行緒不安全版本以保證效能,在多執行緒環境中使用執行緒安全版本。

釋放同步監視器鎖定?

任何