引子
由於多線程共享同一資源(臨界資源),使得多線程程序結果會有不確定性。
怎麼解決不確定性呢?以下兩種方式可以部分控制不確定性:
線程互斥
線程同步
在熟悉一下兩個概念:
臨界區:用synchronized標記的代碼段
臨界資源:被臨界區競爭的訪問的資源
線程互斥
鎖機制
線程互斥是使用鎖機制來實現的,來看看鎖機制:
-
標記出訪問共享資源的代碼段(Java就是用synchronized來標記代碼段的,synchronized是個關鍵字),指明這段代碼將作爲一個整體以原子方式來訪問共享資源;
-
給被訪問的資源關聯上一把鎖;
-
當標記的的代碼段(臨界區)訪問共享資源(臨界資源)前,首先必須獲得對象關聯的鎖;獲得鎖後將鎖鎖閉(lock),並開始實施訪問;在標記的代碼段訪問結束後,釋放鎖;然後別的代碼段就可以訪問這個資源了。
-
只有對象纔有鎖,基本數據類型沒有鎖。
-
沒有使用synchronized標記的代碼段,鎖機制不起作用。
-
無論是synchronized正常結束還是異常退出,都會釋放鎖。
使用格式
Synchronized標記方式有兩種:
-
synchronized(obj)area ; //obj是臨界資源【一個對象】,area是臨界區【一段代碼】。
-
synchronized方法聲明
//比如:publicsynchronized void function(){……}
//等價於publicvoid function(){synchronized(this){area}}(第一種表達方式)
談到線程互斥問題有個很經典的案例就是銀行存錢取錢問題,來實現一下:
/**
* 存錢取錢問題
* 分析:
* 賬戶類Account 取錢線程類Saver 存錢線程類Fetcher
* 臨界資源->賬戶類的實例 臨界區->存取錢動作
* @author jin
*
*/
public class TakeSavingMoney {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Account account=new Account("晉瑜", 5000000);
// 存入1000000
(new Saver(account, 1000000)).start();
// 取出30000
(new Fetcher(account, 30000)).start();
// 取出450000
(new Fetcher(account, 450000)).start();
// 存入50000
(new Saver(account, 50000)).start();
}
}
class Account{ // 賬戶類
private String name; // 賬戶名
private double money; // 賬戶餘額
public Account(String name, double money) {
// TODO Auto-generated constructor stub
this.name=name;
this.money=money;
}
public String getName(){
return this.name;
}
public double getMoney(){
return this.money;
}
public void put(double money){ // 存錢
this.money+=money;
}
public void get(double money){ // 取錢
this.money-=money;
}
}
class Saver extends Thread{ //存錢類
private Account account; // 存錢人擁有一個賬戶
private double money; // 將要存錢的數
public Saver(Account account, double money) {
// TODO Auto-generated constructor stub
this.account=account;
this.money=money;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (account) { // account是臨界資源
//臨界區
System.out.println(account.getName()+"賬戶\n"+"現有:"+account.getMoney()+"元\n"+"存入:"+this.money+"元.");
account.put(this.money);
System.out.println("現有餘額:"+account.getMoney());
}
}
}
class Fetcher extends Thread{ //取錢類
private Account account; // 取款人持有一個賬戶
private double money; // 將要取錢的數
public Fetcher(Account account, double money) {
// TODO Auto-generated constructor stub
this.account=account;
this.money=money;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (account) {
//臨界區
System.out.println(account.getName()+"賬戶\n"+"現有:"+account.getMoney()+"元\n"+"取出:"+this.money+"元.");
account.get(this.money);
System.out.println("現有餘額:"+account.getMoney());
}
}
}
運行結果:
晉瑜賬戶
現有:5000000.0元
存入:1000000.0元.
現有餘額:6000000.0
晉瑜賬戶
現有:6000000.0元
存入:50000.0元.
現有餘額:6050000.0
晉瑜賬戶
現有:6050000.0元
取出:450000.0元.
現有餘額:5600000.0
晉瑜賬戶
現有:5600000.0元
取出:30000.0元.
現有餘額:5570000.0