Java基礎-多線程-③線程同步之synchronized


使用線程同步解決多線程安全問題

  上一篇 Java基礎-多線程-②多線程的安全問題 中我們說到多線程可能引發的安全問題,原因在於多個線程共享了數據,且一個線程在操作(多為寫操作)數據的過程中,另一個線程也對數據進行了操作,從而導致數據出錯。由此我們想到一個解決的思路:將操作共享數據的代碼行作為一個整體,同一時間只允許一個線程執行,執行過程中其他線程不能參與執行。線程同步就是用來實現這樣的機制。

synchronized代碼塊

  Java中提供了synchronized關鍵字,將可能引發安全問題的代碼包裹在synchronized代碼塊中,表示這些代碼需要進行線程同步。synchronized代碼塊的語法格式為:

1 synchronized (expression){
2     //需要同步的代碼
3 }

其中,expression必須是一個引用類型的變量,這里我們可以理解為任意的一個Java對象,否則編譯出錯。下面的例子中我們使用了一個Object對象obj。

 1 class Dog implements Runnable {
 2     private int t = 100;
 3     private Object obj = new Object();
 4 
 5     @Override
 6     public void run() {
 7         while (true) {
 8 
 9             synchronized (obj) {
10                 if (t > 0) {
11                     try {
12                         Thread.sleep(100);
13                         System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + t--);
14                     } catch (InterruptedException e) {}
15                 }
16             }
17 
18         }
19     }
20 }

這時候,一個線程在執行完整個代碼塊(或者非正常結束)之后,其他的線程才有機會進入代碼塊執行,就不會出現“打印的t小於1”的情況了,簡單的實現了代碼的同步。

線程同步的機制和同步鎖

  上面線程同步的效果是怎么實現的呢?Java中任意的對象都可以作為一個監聽器(monitor),監聽器可以被上鎖和解鎖,在線程同步中稱為同步鎖,且同步鎖在同一時間只能被一個線程所持有。上面的obj對象就是一個同步鎖,分析一下上面代碼的執行過程:

  • 一個線程執行到synchronized代碼塊,首先檢查obj,如果obj為空,拋出NullPointerExpression異常;
  • 如果obj不為空,線程嘗試給監聽器上鎖,如果監聽器已經被鎖,則線程不能獲取到鎖,線程就被阻塞;
  • 如果監聽器沒被鎖,則線程將監聽器上鎖,並且持有該鎖,然后執行代碼塊;
  • 代碼塊正常執行結束或者非正常結束,監聽器都將自動解鎖;

所以,一個線程執行代碼塊時,持有了同步鎖,其他線程就不能獲取到鎖,也就不能進入代碼塊執行,只能等待鎖被釋放。這時候我們思考這樣一個問題:在synchronized代碼塊中如果我們每次傳入的都是一個新的對象,能否實現同步的效果呢?如下:

 1     public void run() {
 2         while (true) {
 3             synchronized (new Object()) {
 4                 if (t > 0) {
 5                     try {
 6                         Thread.sleep(100);
 7                         System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + t--);
 8                     } catch (InterruptedException e) {}
 9                 }
10             }
11         }
12     }

顯然多個線程檢查的都是一個新的對象,不同的同步鎖對不同的線程不具有排他性,不能實現線程同步的效果,這時候線程同步就失效了。所以線程同步的一個前提:線程同步鎖對多個線程必須是互斥的,即多個線程需要使用同一個同步鎖。第一段代碼中obj對象被多個線程共享,能夠實現同步。

synchronized方法

  除了synchronized代碼塊,synchronized關鍵字還可以修飾方法,讓該方法進行線程同步,效果跟同步代碼塊一樣。

 1     public synchronized void run() {
 2         while (true) {
 3             if (t > 0) {
 4                 try {
 5                     Thread.sleep(100);
 6                     System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + t--);
 7                 } catch (InterruptedException e) {}
 8             }
 9         }
10     }

這時候synchronized后面沒有了expression,從哪兒獲取同步鎖呢?

  • 對於實例的同步方法,使用this即當前實例對象,如上面的dog;
  • 對於靜態的同步方法,使用當前類的字節碼對象,如上面的Dog.class。

也就是說使用同步方法的話,同步鎖只能是this或者當前類的字節碼對象。所以根據同步鎖必須互斥的前提,如果同時使用synchronized代碼塊和synchronized方法對同一個共享資源進行線程同步,synchronized代碼塊的同步鎖也必須跟synchronized方法一樣(要么是this,要么是類的字節碼對象)。

同步代碼塊和同步方法的區別

  兩者的區別主要體現在同步鎖上面。對於實例的同步方法,因為只能使用this來作為同步鎖,如果一個類中需要使用到多個鎖,為了避免鎖的沖突,必然需要使用不同的對象,這時候同步方法不能滿足需求,只能使用同步代碼塊(同步代碼塊可以傳入任意對象);或者多個類中需要使用到同一個鎖,這時候多個類的實例this顯然是不同的,也只能使用同步代碼塊,傳入同一個對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM