黑馬程式設計師 關於執行緒的瞭解二
----------android培訓、java培訓、java學習型技術部落格、期待與您交流!----------
互斥與同步
引子
由於多執行緒共享同一資源(臨界資源),使得多執行緒程式結果會有不確定性。
怎麼解決不確定性呢?以下兩種方式可以部分控制不確定性:
執行緒互斥
執行緒同步
在熟悉一下兩個概念:
臨界區:用synchronized標記的程式碼段
臨界資源:被臨界區競爭的訪問的資源
執行緒互斥
鎖機制
執行緒互斥是使用鎖機制來實現的,來看看鎖機制:
-
標記出訪問共享資源的程式碼段(Java就是用synchronized來標記程式碼段的,synchronized
-
給被訪問的資源關聯上一把鎖;
-
當標記的的程式碼段(臨界區)訪問共享資源(臨界資源)前,首先必須獲得物件關聯的鎖;獲得鎖後將鎖鎖閉(lock),並開始實施訪問;在標記的程式碼段訪問結束後,釋放鎖;然後別的程式碼段就可以訪問這個資源了。
-
只有物件才有鎖,基本資料型別沒有鎖。
-
沒有使用synchronized標記的程式碼段,鎖機制不起作用。
-
無論是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());
}
}
}
}
總結:主要的是理解同步機制下的互斥和同步。