1. 程式人生 > >Java執行緒之Synchronized同步————Java學習之路(17)

Java執行緒之Synchronized同步————Java學習之路(17)

前言——不進則退啊,部落格幾天沒寫,排名第一次下降了,得勤奮更新,不能偷懶。。

歡迎轉載,轉載請註明來處。

目錄

一.演示同步問題

此前,我們用的多執行緒都是較為簡單的例子,都沒有涉及到多個執行緒共享同一個物件或者資源的情況。倘若多執行緒共享資源的情況下,可能會產生一些“髒資料”。試著考慮下面銀行存取款程式:

class Account{
	
	
	String holderName;
	float amount;
	
	public Account() {
		
	}
	public Account(String n ,float x) {
		
		holderName = n;
		amount = x;
	}
	
	public void deposit(float x) {
		
		amount += x;
	}
	
	public void withdraw(float x) {
		
		amount -= x;
	}
	
	public float getAmount() {
		
		return amount;
	}
}
class drawThread implements Runnable{ //用於取300元的執行緒類
	
	Account  account;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public void run() {
		
		if(account.getAmount() >=300) { //語句1
			
	        account.withdraw(300);               //語句2
			System.out.println("本次取了300後,餘額為:" + account.getAmount() );//語句3
	
		}else {
			
			System.out.println("本次餘額不足");//語句4
		}
	}
}

我們建立一個賬戶,初始amount為500元,再建立兩個執行緒,  執行緒T1用來取款300元,執行緒T2也用來取款300元,模擬兩個人同時取300的情況:

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		
		drawThread  myDraw = new drawThread(myAccount);
		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		T1.start();
		T2.start();
		

	}

}
執行結果:
本次取了300後,餘額為:200.0
本次取了300後,餘額為:-100.0

二.分析同步問題產生的原因

上面的這個執行結果有多種組合,但上面的這種結果顯然是錯誤的,一個500元的賬號是無法兩次都分別取出300的。錯誤的原因是什麼呢? 這是由於T1和T2獲得CPU的時機是隨機的:

T1獲得了CPU,判斷了語句1,滿足了餘額>=300元

T2獲得了CPU,判斷了語句1,也滿足了餘額>=300元

T1獲得了CPU,執行了語句2和語句3,扣了300,餘額為200.

T2獲得了CPU,執行了語句4,扣了300,餘額位-100.

三.解決思路

總體的解決思路:在T1執行緒訪問myAccount這個賬戶時,禁止T2執行緒訪問myAccount.

T1獲得了CPU,判斷了語句1,滿足了餘額>=300元

此時T2獲得了CPU,也要訪問myAccount,但是T1前面沒有訪問結束,所以不允許T2訪問

T1獲得了CPU,執行了語句2和語句3,完成了取錢,餘額還剩200. 結束了對myAccount的訪問。

T2獲得了CPU,此時T1已經訪問完了,因此可以訪問myAccount了,判斷語句1, 餘額<300,執行語句4

這樣結果就能正確了。

四.synchroniazed關鍵字

解決問題之前,我們要先介紹一下synchroniazed關鍵字:

Java語言中的一個關鍵字,可用來給物件和方法或者程式碼塊加鎖,無論鎖的是方法還是程式碼塊,本質上鎖的都是這個方法或者程式碼塊所屬的物件

1.synchronized 同步程式碼塊

synchronized(Object  someObject){

}

someObject物件叫做同步物件,由於Object是所有類的基類,因此任何物件都可以做作為同步物件。一個執行緒如果要執行同步程式碼塊的內容,必須要先佔有同步物件。而someObject在同一時間,只能被一個執行緒佔有,因此間接地同步程式碼塊在同一個時間,只能被同一個程序佔有,其他程序如果嘗試訪問,就會進入等待,直到其他程序釋放同步物件。

此時這個someObject物件其實相當於一把鑰匙,類似於一個標記,有了這個標記,我才能去執行同步程式碼塊的內容。

2.synchronized方法

public synchronized 返回型別 方法名() {

}

其實本質是鎖物件:

public 返回型別 方法名() {

     synchronized(this){ //鎖定的是當前方法的物件

         程式碼語句

    }

}

五.使用synchronized同步物件 解決同步問題,防止出現"髒資料"

1.用Object類物件作為同步物件。

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		Object object1 = new Object(); //注意看這句
		
		drawThread  myDraw = new drawThread(myAccount);
		myDraw.object = object1;
		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		
		T1.start();
		T2.start();
		
		
		

	}

}

class drawThread implements Runnable{ //用於取300元的執行緒類
	
	Account  account;
	Object   object;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public void run() {
		
	  synchronized(object) {  //任何執行緒要取300,都要先佔有object
		  
		     if(account.getAmount() >=300) {
			
	        	
	        	account.withdraw(300);
	        
			System.out.println("本次取了300後,餘額為:" + account.getAmount() );
	
		   }else {
			
			System.out.println("本次餘額不足");
		   }
	  }
	}
}


2.用Account類物件作為同步物件。

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		
		drawThread  myDraw = new drawThread(myAccount);

		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		
		T1.start();
		T2.start();
		
		
		

	}

}


class drawThread implements Runnable{ //用於取300元的執行緒類
	
	Account  account;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public void run() {
		
	  synchronized(account) {  //任何執行緒要取300,都要先佔有當前的account
		  
		     if(account.getAmount() >=300) {
			
	        	
	        	account.withdraw(300);
	        
			System.out.println("本次取了300後,餘額為:" + account.getAmount() );
	
		   }else {
			
			System.out.println("本次餘額不足");
		   }
	  }
	}
}


六.使用synchronized方法 解決同步問題,防止出現"髒資料"

在run()方法前面加上synchronized

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		
		drawThread  myDraw = new drawThread(myAccount);

		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		
		T1.start();
		T2.start();
		
		
		

	}

}


class drawThread implements Runnable{ //用於取300元的執行緒類
	
	Account  account;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public synchronized void run() { //當一個執行緒正在執行drawThread物件的run方法,另一個執行緒 
                                      無法執行run方法
	
		  
		     if(account.getAmount() >=300) {
			
	        	
	        	account.withdraw(300);
	        
			System.out.println("本次取了300後,餘額為:" + account.getAmount() );
	
		   }else {
			
			System.out.println("本次餘額不足");
		   }
	  
	}
}