執行緒同步機制 ★★★★
執行緒同步機制★★★★
1、synchronized-執行緒同步
執行緒同步機制的語法是:
synchronized(){
// 執行緒同步程式碼塊。
}
synchronized()小括號內容是至關重要的,它必須得是要同步的多個執行緒所共享的資料
舉個例子:
假設目前程式內共有t1到t5五個執行緒,我只想讓t1,t2,t3排隊,其餘執行緒不排隊,要怎麼辦呢?
一定要在()中寫一個t1 t2 t3共享的物件。而這個物件對於t4 t5來說不是共享的。
在java語言中,任何一個物件都有“一把鎖”,其實這把鎖就是標記。100個物件,100把鎖。1個物件1把鎖。
2、eg.理解執行緒同步
/** * 通過程式碼來理解執行緒同步synchronized * 多執行緒共享同一銀行賬戶,取錢 */ public class Test { public static void main(String[] args) { // 建立賬戶物件(只建立1個) Account act = new Account("act-001", 10000); // 建立兩個執行緒,共享同一個物件 Thread t1 = new Account.AccountThread(act); Thread t2 = new Account.AccountThread(act); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } static class Account { private String actno;//賬戶編號 private double balance;//賬戶餘額 //物件 Object o = new Object(); // 例項變數。(Account物件是多執行緒共享的,Account物件中的例項變數obj也是共享的。) public Account() { } public Account(String actno, double balance) { this.actno = actno; this.balance = balance; } public String getActno() { return actno; } public void setActno(String actno) { this.actno = actno; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } //取款方法 public void withdraw(double money) { /** * 以下可以共享,金額不會出錯 * 以下這幾行程式碼必須是執行緒排隊的,不能併發。 * 一個執行緒把這裡的程式碼全部執行結束之後,另一個執行緒才能進來。 */ synchronized (this) { //synchronized(actno) { //synchronized(o) { /** * 以下不共享,金額會出錯 */ //Object obj2 = new Object(); //synchronized(obj2) { // 這樣編寫就不安全了。因為obj2不是共享物件。 //String s = null; //synchronized(null) {//編譯不通過 //synchronized(s) {//java.lang.NullPointerException double before = this.getBalance(); double after = before - money; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); //} } } static class AccountThread extends Thread { // 兩個執行緒必須共享同一個賬戶物件。 private Account act; // 通過構造方法傳遞過來賬戶物件 public AccountThread(Account act) { this.act = act; } public void run() { double money = 5000; act.withdraw(money); System.out.println(Thread.currentThread().getName() + "對" + act.getActno() + "取款" + money + "成功,餘額" + act.getBalance()); } } } }
如何判斷是否共享?
- 如果某個物件是多個執行緒所共享的,那麼該物件中的例項變數自然也是這幾個執行緒所共享的,如程式碼中的下圖部分
/**
* 以下可以共享,金額不會出錯
* 以下這幾行程式碼必須是執行緒排隊的,不能併發。
* 一個執行緒把這裡的程式碼全部執行結束之後,另一個執行緒才能進來。
*/
synchronized (this)
synchronized(actno)
synchronized(o)
- 在新方法中建立的例項變數不屬於線上程方法中定義的物件Account的例項變數,這些例項變數自然就不是多個執行緒所共享的
2、執行結果
觀察以下幾張圖片的執行結果,證明物件或例項變數是否共享
以上程式碼鎖this、例項變數actno、例項變數o都可以!因為這三個被多個執行緒共享
共享
檢視結果可知,t1,t2兩個執行緒排隊對act-001賬戶物件進行取款操作,說明this為多執行緒共享
檢視結果可知,t1,t2兩個執行緒排隊對act-001賬戶物件進行取款操作,說明例項變數actno為多執行緒共享
檢視結果可知,t1,t2兩個執行緒排隊對act-001賬戶物件進行取款操作,說明例項變數o為多執行緒共享
不共享
用同樣的方式測試後發現,不屬於執行緒方法中定義的物件Account的例項變數都是不共享的
obj2,null,s都不共享
3、執行緒同步原理
-
假設t1和t2執行緒併發,開始執行程式碼的時候,肯定有一個先一個後
-
假設t1先執行了,遇到了synchronized,這個時候自動找“共享物件”的物件鎖,
找到之後,並佔有這把鎖,然後執行同步程式碼塊中的程式,在程式執行過程中一直都是
佔有這把鎖的。直到同步程式碼塊程式碼結束,這把鎖才會釋放。 -
假設t1已經佔有這把鎖,此時t2也遇到synchronized關鍵字,也會去佔有共享物件的這把鎖
結果這把鎖被t1佔有,t2只能在同步程式碼塊外面等待t1的結束,
直到t1把同步程式碼塊執行結束了,t1會歸還這把鎖,此時t2終於等到這把鎖,然後
t2佔有這把鎖之後,進入同步程式碼塊執行程式。這樣就實現了執行緒排隊執行。
4、在例項方法中可以使用synchronized(不推薦)
-
舉個例子,在上邊的程式碼中,synchronized是可以在例項方法中使用的,但是,此時的鎖只能是this!沒得挑,只能是this,不能是其他的物件。所以這種方式不靈活。
-
synchronized出現在例項方法上,表示整個方法體都需要同步,可能會無故擴大同步的範圍,導致程式的執行效率降低。所以這種方式不常用。但這種方式可以精簡程式碼。
-
如果共享的物件就是this,並且需要同步的程式碼塊是整個方法體,建議使用這種方式。
1、eg.
在例項方法中使用synchronized
public synchronized void withdraw(double money){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
在方法呼叫處使用synchronized
public void run(){
double money = 5000;
// 取款
// 多執行緒併發執行這個方法。
//synchronized (this) { //這裡的this是AccountThread物件,這個物件不共享!
synchronized (act) { // 這種方式也可以,只不過擴大了同步的範圍,效率更低了。
act.withdraw(money);
}
System.out.println(Thread.currentThread().getName() + "對"+act.getActno()+"取款"+money+"成功,餘額" + act.getBalance());
}
2、疑問(待解決)
為什麼this不共享?執行發現也沒有問題啊?待解決