簡體   English   中英

為什么同步塊比同步方法更好?

[英]Why is synchronized block better than synchronized method?

我已經開始學習線程同步。

同步方法:

public class Counter {

   private static int count = 0;

   public static synchronized int getCount() {
      return count;
   }

   public synchronized setCount(int count) {
      this.count = count;
   }

}

同步塊:

public class Singleton {

   private static volatile Singleton _instance;

   public static Singleton getInstance() {
      if (_instance == null) {
         synchronized(Singleton.class) {
            if (_instance == null)
               _instance = new Singleton();
         }
      }
      return _instance;
   }
}

什么時候應該使用synchronized方法和synchronized塊?

為什么synchronized塊比synchronized方法更好?

這不是更好的問題,只是不同。

當您同步一個方法時,您實際上是在同步到對象本身。 在靜態方法的情況下,您正在同步到對象的類。 所以下面兩段代碼的執行方式是一樣的:

public synchronized int getCount() {
    // ...
}

這就像你寫的一樣。

public int getCount() {
    synchronized (this) {
        // ...
    }
}

如果要控制與特定對象的同步,或者只想將方法的一部分同步到該對象,則指定一個synchronized塊。 如果在方法聲明中使用了synchronized關鍵字,它會將整個方法同步到對象或類。

雖然通常不是一個問題,但從安全角度來看,最好在私有對象上使用同步,而不是將它放在方法上。

將它放在方法上意味着您正在使用對象本身的鎖來提供線程安全。 通過這種機制,您代碼的惡意用戶也有可能獲得您對象的鎖,並永久持有它,從而有效地阻塞其他線程。 非惡意用戶可以在不經意間有效地做同樣的事情。

如果您使用私有數據成員的鎖,則可以防止這種情況發生,因為惡意用戶不可能獲得您私有對象的鎖。

private final Object lockObject = new Object();

public void getCount() {
    synchronized( lockObject ) {
        ...
    }
}

Bloch 的 Effective Java(第 2 版)第 70 項中提到了這種技術

不同之處在於獲取的是哪個鎖:

  • 同步方法獲取整個對象的鎖。 這意味着當一個線程正在運行該方法時,沒有其他線程可以在整個對象中使用任何同步方法。

  • synchronized 塊在synchronized 關鍵字之后的括號之間獲取對象中的鎖。 這意味着在同步塊退出之前,沒有其他線程可以獲取鎖定對象的鎖定。

因此,如果您想鎖定整個對象,請使用同步方法。 如果您想讓其他線程可以訪問對象的其他部分,請使用同步塊。

如果仔細選擇鎖定的對象,同步塊將導致較少的爭用,因為整個對象/類都沒有被阻塞。

這同樣適用於靜態方法:同步靜態方法將獲取整個類對象中的鎖,而靜態方法內的同步塊將獲取括號之間對象中的鎖。

同步塊同步方法的區別如下:

  1. 同步塊減少了鎖的范圍,同步方法的鎖范圍是整個方法。
  2. 同步塊具有更好的性能,因為只有臨界區被鎖定,同步方法的性能比塊差。
  3. 同步塊提供對鎖的細粒度控制,同步方法鎖定由 this 或類級鎖表示的當前對象。
  4. 同步塊可以拋出 NullPointerException同步方法不會拋出。
  5. 同步塊: synchronized(this){}

    同步方法: public synchronized void fun(){}

定義“更好”。 同步塊只是更好,因為它允許您:

  1. 在不同的對象上同步
  2. 限制同步范圍

現在,您的具體示例是可疑的雙重檢查鎖定模式的示例(在較舊的 Java 版本中,它已損壞,並且很容易出錯)。

如果您的初始化成本較低,最好立即使用 final 字段進行初始化,而不是在第一次請求時進行初始化,這也將消除同步的需要。

僅當您希望您的類是線程安全的時才應使用同步 事實上,大多數類無論如何都不應該使用同步。 同步方法只會在此對象上提供鎖定,並且僅在其執行期間提供鎖定 如果你真的想讓你的類線程安全,你應該考慮讓你的變量可變同步訪問。

使用同步方法的問題之一是該類的所有成員都將使用相同的,這會使您的程序變慢。 在您的情況下,同步方法和塊將執行沒有什么不同。 我會推薦的是使用專用並使用類似這樣的同步塊

public class AClass {
private int x;
private final Object lock = new Object();     //it must be final!

 public void setX() {
    synchronized(lock) {
        x++;
    }
 }
}

在你的情況下,兩者都是等價的!

同步一個靜態方法相當於在相應的 Class 對象上同步一個塊。

實際上當你聲明一個synchronized靜態方法時,是在Class對象對應的monitor上獲得鎖的。

public static synchronized int getCount() {
    // ...
}

public int getCount() {
    synchronized (ClassName.class) {
        // ...
    }
}

因為鎖是昂貴的,當你使用同步塊時,你只在_instance == null鎖定,並且在_instance最終初始化之后你將永遠不會鎖定。 但是當您同步方法時,您會無條件鎖定,即使在_instance初始化之后也是如此。 這是雙重檢查鎖定優化模式http://en.wikipedia.org/wiki/Double-checked_locking背后的想法。

不應將其視為最佳使用問題,但它確實取決於用例或場景。

同步方法

可以將整個方法標記為同步,從而導致對 this 引用(實例方法)或類(靜態方法)的隱式鎖定。 這是實現同步的非常方便的機制。

步驟線程訪問同步方法。 它隱式地獲取鎖並執行代碼。 如果其他線程要訪問上述方法,則必須等待。 線程無法獲得鎖,將被阻塞,必須等到鎖被釋放。

同步塊

要為一組特定的代碼塊獲取對象上的鎖,同步塊是最合適的。 由於一個塊就足夠了,使用同步方法將是一種浪費。

更具體地說,使用 Synchronized Block ,可以定義要獲取鎖的對象引用。

Synchronized 塊和 Synchronized 方法之間的一個經典區別是 Synchronized 方法鎖定整個對象。 同步塊只是鎖定塊內的代碼。

同步方法:基本上這兩種同步方法禁用多線程。 所以一個線程完成method1(),另一個線程等待Thread1 完成。

類 SyncExerciseWithSyncMethod {

public synchronized void method1() {
    try {
        System.out.println("In Method 1");
        Thread.sleep(5000);
    } catch (Exception e) {
        System.out.println("Catch of method 1");
    } finally {
        System.out.println("Finally of method 1");
    }

}

public synchronized void method2() {
    try {
        for (int i = 1; i < 10; i++) {
            System.out.println("Method 2 " + i);
            Thread.sleep(1000);
        }
    } catch (Exception e) {
        System.out.println("Catch of method 2");
    } finally {
        System.out.println("Finally of method 2");
    }
}

}

輸出

在方法 1 中

方法一的最后

方法 2 1

方法 2 2

方法 2 3

方法 2 4

方法 2 5

方法 2 6

方法 2 7

方法 2 8

方法 2 9

方法二的最后


同步塊:允許多個線程同時訪問同一個對象【啟用多線程】。

類 SyncExerciseWithSyncBlock {

public Object lock1 = new Object();
public Object lock2 = new Object();

public void method1() {
    synchronized (lock1) {
        try {
            System.out.println("In Method 1");
            Thread.sleep(5000);
        } catch (Exception e) {
            System.out.println("Catch of method 1");
        } finally {
            System.out.println("Finally of method 1");
        }
    }

}

public void method2() {

    synchronized (lock2) {
        try {
            for (int i = 1; i < 10; i++) {
                System.out.println("Method 2 " + i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.out.println("Catch of method 2");
        } finally {
            System.out.println("Finally of method 2");
        }
    }
}

}

輸出

在方法 1 中

方法 2 1

方法 2 2

方法 2 3

方法 2 4

方法 2 5

方法一的最后

方法 2 6

方法 2 7

方法 2 8

方法 2 9

方法二的最后

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM