簡體   English   中英

我應該嘗試避免靜態同步方法

[英]Should I try to avoid static synchronized method

根據我的理解,以下代碼效率不高:

class Foo {
    static Resource resource1;
    static Resource resource2;

    static synchronized void methodA() {
       resource1.add("abc");
    }

    static synchronized void methodB() {
       resource2.add("abc");
    }
}

根據我的理解,兩種方法都鎖定在一個對象(類對象Foo.class )中,所以我猜測以下是一個很好的優化?

class Foo {
    static Resource resource1;
    static Resource resource2;

    static void methodA()  {
       synchronized(resource1) {
           resource1.add("abc");
       }
    }

    static void methodB()  {
       synchronized(resource2) {
           resource2.add("123");
       }
    }
}

只要這兩種資源不相互依賴。

我什么時候應該考慮使用靜態同步方法?

當類抽象訪問單個關鍵資源時使用static synchronized構造,因此鎖定類在語義上是正確的。

如果您的類抽象訪問多個關鍵資源,那么您必須使用更精細的鎖定,如您的示例所示。

您可以將方法的synchronized修飾符視為語法糖,除了鎖定類(或方法不是靜態的實例)之外,沒有額外的黑魔法。

在您的第一個示例中,如果單個類提供對兩個不同關鍵資源的訪問,如果它們完全不相關,那么這是值得懷疑的。 也許您可以將關鍵部分移動到Resource類本身。

您的優化是正確的。

第一個代碼鎖定在Foo.class

第二個代碼鎖定兩個不同的對象: resource1resource2

從視覺上你可以想象這一點

第一個代碼:

 Thread 1              Thread 2
 ------------------------------

 Foo.methodA()

                        Foo.methodB()   
 // A call to methodB needs to wait for completion of methodA

第二個代碼:

 Thread 1              Thread 2
 ------------------------------

 Foo.methodA()         Foo.methodB()    
 // At the same time in a machine with at least a dual core

只有在有一個資源要同步時,才應考慮使用靜態同步方法。

優化很好,但要注意可能的死鎖。 在示例中,有時您將決定訪問這兩種資源:

class Foo {
    static Resource resource1;
    static Resource resource2;

    static void methodA()  {
       synchronized(resource1) {
           resource1.add("abc");
           synchronized(resource2) {
               resource2.add("abc");
           }
       }
    }

    static void methodB()  {
       synchronized(resource2) {
           resource2.add("123");
           synchronized(resource1) {
               resource1.add("123");
           }
       }
    }
}

這可能導致死鎖:

  1. 線程A嘗試執行methodA();
  2. 線程B嘗試同時執行methodB();
  3. 線程A獲取resource1鎖定,線程B獲取resource2鎖定
  4. 線程A嘗試獲取resource2鎖定,並開始等待鎖定釋放
  5. 線程B嘗試獲取resource1鎖定,並開始等待鎖定釋放
  6. 僵局

為避免這種情況,您可以使Resource類成為線程安全的:

class Resource {
    private final Object mLock = new Object();
    ...
    public void add(String str) {
        synchronized(mLock) {
            //do stuff
        }
    }
}

第二種方法(鎖定對象)是首選,因為它可以讓您更好地控制何時應該進行鎖定。 更重要的是,它可以防止您的類的外部方無限期地鎖定您的類,從而阻止您自己的方法執行。

請考慮以下內容:想象一些外部代碼包含以下語句:

 synchronized (Foo.class) {
     Thread.sleep(10000);
 }

現在,如果您在方法1中使用了類方法本身的synchronized,那么同時嘗試調用methodA或methodB的其他類將被阻塞,直到睡眠完成。 但是,如果您在方法2中使用內部對象的內部鎖定,那么其他類將不必等待。

由於上述原因,我不會真正推薦方法1。

PS我剛剛注意到方法2中的內部鎖沒有被聲明為final。 如果在方法忙於鎖定它們時重新分配它們將是一個問題(然后鎖將在不同的實例上)。 為了防止這種情況,請將它們聲明為final,如下所示:

final static Resource resource1 = new Resource(...);
final static Resource resource2 = new Resource(...);

簡而言之,永遠不要在非最終對象上同步。

暫無
暫無

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

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