1. 程式人生 > >黑馬程式設計師 關於執行緒的瞭解二

黑馬程式設計師 關於執行緒的瞭解二

----------android培訓java培訓、java學習型技術部落格、期待與您交流!----------

互斥與同步

引子

由於多執行緒共享同一資源(臨界資源),使得多執行緒程式結果會有不確定性。

怎麼解決不確定性呢?以下兩種方式可以部分控制不確定性:

執行緒互斥

執行緒同步

在熟悉一下兩個概念:

臨界區:用synchronized標記的程式碼段

臨界資源:被臨界區競爭的訪問的資源

執行緒互斥

鎖機制

執行緒互斥是使用鎖機制來實現的,來看看鎖機制:

  1. 標記出訪問共享資源的程式碼段(Java就是用synchronized來標記程式碼段的,synchronized

    是個關鍵字),指明這段程式碼將作為一個整體以原子方式來訪問共享資源;

  2. 給被訪問的資源關聯上一把鎖;

  3. 當標記的的程式碼段(臨界區)訪問共享資源(臨界資源)前,首先必須獲得物件關聯的鎖;獲得鎖後將鎖鎖閉(lock),並開始實施訪問;在標記的程式碼段訪問結束後,釋放鎖;然後別的程式碼段就可以訪問這個資源了。

  4. 只有物件才有鎖基本資料型別沒有鎖。

  5. 沒有使用synchronized標記的程式碼段,鎖機制不起作用。

  6. 無論是synchronized正常結束還是異常退出,都會釋放鎖。

使用格式

Synchronized標記方式有兩種:

  • synchronized(obj)area ; //obj是臨界資源【一個物件】,area

    是臨界區【一段程式碼】。

  • synchronized方法宣告

    //比如:publicsynchronized void function(){……}

    //等價於publicvoid function(){synchronized(this){area}}(第一種表達方式)

執行緒互斥的一個經典例項(模型:同一個人的不同動作)

題目:用多執行緒互斥的思想實現對某個銀行賬戶的存取款操作的設計。

//建立賬戶
class Account{
	private String name;//戶主姓名
	private int balance;//餘額,模擬ATM取款為整數
	
	public Account(String name,int balance) {
		this.name = name;
		this.balance = balance;
	}
	public String getName() {
		return name;
	}
	public int getBalance() {
		return balance;
	}
	public void saveMoney(int money){//存款
		this.balance += money;
	}
	public int withdrawMoney(int money){//取款
		if (money<=this.balance) {
			this.balance -= money;
			return money;
		}
		System.out.println("當前餘額為:"+this.balance+"元,預取款:"+money+"元,餘額不足!");
		return 0;
	}	
}

class SaveMoney extends Thread{//存款執行緒類
	private Account account;
	private int money;
	public SaveMoney(Account account, int money) {
		this.account = account;
		this.money = money;
	}
	public synchronized void run(){
		 
			int balance = this.account.getBalance();//獲取存款前的餘額
			this.account.saveMoney(money);
			System.out.println("賬戶:"+this.account.getName()+",餘額:"+
					balance+"元,此次存入:"+money+"元,現餘額:"+account.getBalance()+"元");
		
	}
}

class WithdrawMoney extends Thread{//取款執行緒類
	private Account account;
	private int money;
	public WithdrawMoney(Account account, int money) {
		this.account = account;
		this.money = money;
	}
	public synchronized void  run(){
			int balance = this.account.getBalance();//獲取取款前的餘額
			
			System.out.println("賬戶:"+this.account.getName()+",餘額:"+
					balance+"元,此次取款:"+this.account.withdrawMoney(money)+"元,現餘額:"+this.account.getBalance()+"元");
	}
}

public class SaveWithdrawMoney{
	public static void main(String[] args) {
		Account account = new Account("張三",0);//賬戶初始時餘額為零
		new SaveMoney(account,300).start();
		new SaveMoney(account,600).start();
		new WithdrawMoney(account,200).start();
		new WithdrawMoney(account,15000).start();
	}
}

說明:該例中,使用者的存或取的操作先後是不確定的,但是該程式存取利用了同一把鎖,保證了存取操作都是針對同一個使用者進行的(從另一個角度說,就是這些操作都是該使用者自己乾的)。


執行緒同步

同步與非同步的概念

在學習執行緒同步前,我們也要理解下面兩個概念:

非同步:多個執行緒的執行互相獨立,彼此間無依賴性;

同步:多個執行緒的執行滿足特定的節奏。

同步實現

synchronized雖然有”同步”的意思,但它實現的首先是互斥機制,講究的是消除對臨界資源訪問的競爭,而不關心訪問執行緒之間的步調。而要實現同步:不僅要消除對臨界資源訪問的競爭,還要關心訪問執行緒之間的步調。

所以,用以下公式來表達同步機制的實現再合適不過了:

Java的同步機制=存取共享資源的互斥機制+執行緒間的通訊機制

存取共享資源的互斥機制我們已經知道了用synchronized來實現了,那執行緒間的通訊怎麼實現呢?

執行緒間的通訊

執行緒間的通訊通過Object類中的方法:wait()notify()notifyAll()來實現。

wait():暫停當前執行緒的執行,並釋放所持有的鎖,進入等待狀態。

notify():喚醒一個等待執行緒。

notifyAll():喚醒所有等待的執行緒。

這三個方法都是Object類的final方法,所以不能被重寫

這三個方法必須要與synchronized一起使用,只能直接或間接地用於臨界區中。

注意:我在網上就看到了有位博友寫的一篇文章(http://blog.csdn.net/zyplus/article/details/6672775),他說”從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內“,這是直接用於臨界區,其實也可以:比如說用在臨界資源的一個方法put()中,但臨界區中有呼叫這個方法就可以了(我下面的那個生產者-消費者案例就是這樣用的)。

執行緒同步的一個經典例項(模型:兩組不同物件對同一共享變數操作,讀寫操作)。

說白了,該情形強調的是先後次序,也就是說必須等我這邊忙完了,你那邊才開始。

題目:用多執行緒同步思想實現對某個共享緩衝區的讀寫操作的設計,即一旦寫入資料,馬上讀走。

public class ReaderAndWrite {

	public static void main(String[] args) {
		
		Buffer buffer = new Buffer();
		String[] datas = {"張三","李四","王五","趙六"};
		new writer(buffer,datas).start();
		new Reader(buffer,datas).start();
	}
}

class Buffer{
	private String data;
	private boolean isNull = true;//讀寫執行緒通訊的訊號量,true表示緩衝區為空,可寫
	public  void putData(String data){
		while (!isNull) {//等待isNull為true,即等待資料被讀走
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.data = data;//此時isNull為true,即前一個數據已被取走
		isNull = false;//將訊號量設為false,表示緩衝區不為空,用以通知讀程序
		notify();//喚醒等待的讀執行緒,以進行讀取剛剛存入的資料
	}
	
	public  String getData(){
		while (isNull) {//此時若isNull為true,即無資料,則等待isNull為false,即等待寫入資料
			
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		isNull = true;//將訊號量設為true,表示緩衝區為空,用以通知寫程序
		notify();//喚醒等待的寫執行緒,以進行寫操作
		return data;//有資料則返回當前資料
	}
}

class writer extends Thread{
	private Buffer buffer;
	private String[] datas;
	public writer(Buffer buffer,String[] datas) {
		this.buffer = buffer;
		this.datas = datas;
	}
	public  void run(){	
		//很明顯,涉及到緩衝區,則可以將表示緩衝區的共享變數設為鎖
		synchronized (buffer) {
			for(int i = 0;i<datas.length;i++){
				buffer.putData(datas[i]);
				System.out.println("寫入:"+datas[i]);	
			}
		}
	}
}


class Reader extends Thread{
	private Buffer buffer;
	private String[] datas;
	public Reader(Buffer buffer,String[] datas) {
		this.buffer = buffer;
		this.datas = datas;
	}
	public void run(){
		//使用與寫操作相同的緩衝區,則設定同一把鎖
		synchronized (buffer) {
			for(int i = 0;i<datas.length;i++){
				System.out.println("讀取:"+buffer.getData());	
			}
		}
	}
}


總結:主要的是理解同步機制下的互斥和同步。


 

----------android培訓java培訓、java學習型技術部落格、期待與您交流!----------