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("本次餘額不足");
}
}
}