併發編程基礎四--同步代碼塊,同步方法,lock,死鎖

老生常談的一個問題,就bank的那個例子,同一張卡同時取錢,併發2個,每一個取800,但卡里只有1000,不能讓2我的同時操做.這裏就須要線程同步.好比下面的DEMO. java

首先定義一個帳戶類: 編程

package org.credo.thread.tongbu;

public class Account {
	private String account;
	private double money;
	
	public Account(String account,double money){
		this.account=account;
		this.money=money;
	}
	
	//根據account重寫hashcode和equals方法.
	public int hasCode(){
		return account.hashCode();
	}
	public boolean equals(Object obj){
		if(this==obj){
			return true;
		}
		if(obj!=null && obj.getClass()==Account.class){
			Account target=(Account)obj;
			return target.getAccount().equals(account);
		}
		return false;
	}
	
	public String getAccount() {
		return account;
	}
	public void setAccount(String account) {
		this.account = account;
	}
	public double getMoney() {
		return money;
	}
	public void setMoney(double money) {
		this.money = money;
	}
}
再就是業務類:
package org.credo.thread.tongbu;

public class Draw extends Thread{
	
	private Account account;
	private double hopeGetMoney;
	
	public Draw(String name,Account account,double hopeGetMoney){
		super(name);
		this.account=account;
		this.hopeGetMoney=hopeGetMoney;
	}
	
	//當多個線程修改同一個共享數據的時候,將會引起數據問題.
	public void run(){
		if(account.getMoney() >= hopeGetMoney){
			System.out.println(account.getAccount()+"---"+getName()+"取錢成功,取出金額爲:"+hopeGetMoney);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//修改金額
			account.setMoney(account.getMoney()-hopeGetMoney);
			System.out.println("餘額爲:"+account.getMoney());
		}else{
			System.out.println("餘額不足,取款失敗!");
		}
	}
}
測試類:
package org.credo.thread.tongbu;

public class NoSynchronizedTest {

	public static void main(String[] args) {
		//create a new account
		Account account=new Account("印度人", 1000);
		new Draw("印度人A", account, 800).start();
		new Draw("印度人B", account, 800).start();
	}
}
結果是:

印度人---印度人B取錢成功,取出金額爲:800.0
印度人---印度人A取錢成功,取出金額爲:800.0
餘額爲:200.0
餘額爲:-600.0 安全

天然是錯的,畢竟這不是信用卡. 多線程

在這種場景下,account這個帳戶必然要是必定時間內只能一我的操做的,是須要線程的同步的. 併發

1.同步代碼塊

以下code:在業務類的run方法總加入synchronized代碼塊. 工具

public void run(){
		//使用account做爲同步監視器,任何線程進入下面同步代碼塊以前必須先得到對account帳戶的鎖定.
		//其餘線程沒法得到鎖,也就沒法修改
		//這種作法符合,  加鎖---修改---釋放鎖   的邏輯.
		synchronized (account) {
			if(account.getMoney() >= hopeGetMoney){
				System.out.println(account.getAccount()+"---"+getName()+"取錢成功,取出金額爲:"+hopeGetMoney);
				try {
					Thread.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
				//修改金額
				account.setMoney(account.getMoney()-hopeGetMoney);
				System.out.println("餘額爲:"+account.getMoney());
			}else{
				System.out.println("餘額不足,取款失敗!");
			}
		}
		//結束,釋放同步鎖.
	}
印度人---印度人A取錢成功,取出金額爲:800.0
餘額爲:200.0
餘額不足,取款失敗!

爲了解決問題,java的處理方法就出現了"同步監視器",使用同步監視器的方法就是上面的同步代碼塊.語法嘛,就同上.

synchronized(obj){
    //此處代碼就是同步代碼塊
} 性能

括號裏的obj就是同步監視器,上面的含義就是,線程開始執行同步代碼前,必須先得到對同步監視器的鎖定.當完成同步代碼塊後會釋放鎖定.
Java容許任何對象充當同步監視器,但實際上,咱們必須使用可能被併發訪問的貢獻資源充當同步監視器.如上面代碼的 account.

2.同步方法

與同步代碼塊對應的,java的多線程的安全支持還提供了同步方法,同步方法就是使用synchronized關鍵字去修飾某個方法.對於同步方法而言,其同步監視器就是this,就是該對象自己. 測試

用過使用同步方法能夠很方便的實現線程安全的類,線程安全的類具備以下特徵: ui

  • 該類的對象能夠被多個線程安全的訪問
  • 每一個線程調用該對象的任意方法都會獲得正確的結果.
  • 每一個線程調用該對象任意方法後,該對象狀態依然保持合理狀態.

synchronized關鍵字能夠修飾方法,代碼塊,但不能修改構造器,屬性等. this

在Account類中加入下面代碼:

public synchronized void process(double hopeGetMoney){
		if(money >= hopeGetMoney){
			System.out.println(Thread.currentThread().getName()+"取錢成功,取出金額爲:"+hopeGetMoney);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//修改金額
			this.money=money-hopeGetMoney;
			System.out.println("餘額爲:"+money);
		}else{
			System.out.println("餘額不足,取款失敗!");
		}
	}

修改業務類中的run()方法爲:

public void run(){
		account.process(hopeGetMoney);
	}
印度人A取錢成功,取出金額爲:800.0
餘額爲:200.0
餘額不足,取款失敗!

可變類的線程安全是以下降程序的運行效率爲代價的.所以若是嚴格考慮性能的話,能夠提供 單線程環境和多線程環境,提供2個版本,,一個就是線程不安全,一個線程安全,分別在單線程和多線程中使用.

好比StringBuffer是多線程安全的,但性能很差.StringBuilder有好的性能,但線程是不安全的.

3.釋放同步監視器的鎖定

釋放:

  • 1.當前線程的同步方法,同步代碼塊正常運行結束.
  • 2.當前線程在同步代碼塊,同步方法中遇到break,return什麼的,
  • 3發生異常,錯誤.
  • 4.程序執行了同步監視器對象的wait()方法,線程暫停並釋放同步監視器.

不釋放:

  • 1.在期內執行了Thread.sleep,thread.yield方法.不會放開.
  • 2.用了suspend掛起,固然不該該用suspend和resume方法.


從JAVA5開始.有了Lock.

先貼代碼,上一章最後的代碼,不變,只修改account代碼以下:

public class Account {
	private String account;
	private double money;
	private final ReentrantLock lock=new ReentrantLock();
	
	public Account(String account,double money){
		this.account=account;
		this.money=money;
	}
	
	public void process(double hopeGetMoney){
		try {
			lock.lock();
		if(money >= hopeGetMoney){
			System.out.println(Thread.currentThread().getName()+"取錢成功,取出金額爲:"+hopeGetMoney);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//修改金額
			this.money=money-hopeGetMoney;
			System.out.println("餘額爲:"+money);
		}else{
			System.out.println("餘額不足,取款失敗!");
		}
		} finally {
			lock.unlock();
		}
	}

	
	//根據account重寫hashcode和equals方法.
使用的是lock.輸出爲:

印度人A取錢成功,取出金額爲:800.0
餘額爲:200.0
餘額不足,取款失敗!

Lock是控制多個線程對共享資源進行訪問的工具.一般,鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對lock對象加鎖,線程開始訪問共享資源以前應得到Lock對象.

同步鎖裏面須要理解的東西也不少,這裏........尼瑪快過年了,實在沒心情搞了.

===============死鎖==================

死鎖就是兩個線程互相等待對方釋放同步監視器時就回發生死鎖.Java虛擬機沒有檢測,也沒有采起措施來處理死鎖狀況,因此多線程編程時要注意這個狀況.發生死鎖的時候,整個程序沒錯誤沒異常,沒提示,只是說有線程都處於阻塞狀態,沒法繼續.

相關文章
相關標籤/搜索