簡體   English   中英

如何跨類使用synchronized塊?

[英]How to use synchronized blocks across classes?

我想知道如何跨類使用synchronized塊。 我的意思是,我想在多個類中同步塊,但它們都在同一個對象上進行同步。 我想到如何做到這一點的唯一方法是這樣的:

//class 1
public static Object obj = new Object();

someMethod(){
     synchronized(obj){
         //code
     }
}


//class 2
someMethod(){
     synchronized(firstClass.obj){
         //code
     }
}

在這個例子中,我創建了一個在第一個類中同步的任意Object,在第二個類中也通過靜態引用它來同步它。 但是,這對我來說似乎很糟糕。 有沒有更好的方法來實現這一目標?

具有用作鎖的靜態對象通常是不可取的,因為整個應用程序中一次只有一個線程可以取得進展。 如果你有多個類共享相同的鎖,甚至更糟,你可以得到一個幾乎沒有實際並發性的程序。

Java在每個對象上具有內部鎖定的原因是對象可以使用同步來保護自己的數據。 線程調用對象上的方法,如果需要保護對象不受並發更改的影響,則可以將synchronized關鍵字添加到對象的方法中,以便每個調用線程必須先獲取該對象的鎖定,然后才能對其執行方法。 這種方式調用不相關的對象不需要相同的鎖,你有更好的機會讓代碼實際並發運行。

鎖定不一定是您的第一個並發技術。 實際上,您可以使用許多技術。 按降序排列順序:

1)盡可能消除可變狀態; 不可變對象和無狀態函數是理想的,因為沒有要保護的狀態,也不需要鎖定。

2)盡可能使用線程限制; 如果您可以將狀態限制為單個線程,則可以避免數據爭用和內存可見性問題,並最大限度地減少鎖定量。

3)使用並發庫和框架優先使用鎖定來滾動自己的對象。 熟悉java.util.concurrent中的類。 與應用程序開發人員可以管理的任何內容相比,它們編寫得更好。

一旦你完成了上面的1,2和3的盡可能多的工作,那么你可以考慮使用鎖定(其中鎖定包括ReentrantLock和內部鎖定等選項)。 將鎖與受保護對象相關聯可以最大限度地減小鎖的范圍,從而使線程不會長時間保持鎖定。

此外,如果鎖不在數據被鎖定的情況下,那么如果在某些時候你決定使用不同的鎖而不是讓所有東西鎖定在同一個東西上,那么避免死鎖可能具有挑戰性。 鎖定需要保護的數據結構使鎖定行為更容易推理。

完全避免內在鎖定的建議可能是過早優化。 首先要確保你鎖定正確的東西不超過必要的。

選項1:

更簡單的方法是使用枚舉或靜態內部類創建單獨的對象(單例)。 然后使用它鎖定兩個類,它看起來很優雅:

// use any singleton object, at it's simplest can use any unique string in double quotes
  public enum LockObj {
    INSTANCE;
  }

  public class Class1 {
    public void someMethod() {
      synchronized (LockObj.INSTANCE) {
        // some code
      }
    }
  }

  public class Class2 {
    public void someMethod() {
      synchronized (LockObj.INSTANCE) {
        // some code
      }
    }
  }

OPTION:2

您可以使用任何字符串,因為JVM確保每個JVM只出現一次。 唯一性是確保此字符串上沒有其他鎖定。 根本不要使用此選項,這只是為了澄清這個概念。

     public class Class1 {
    public void someMethod() {
      synchronized ("MyUniqueString") {
        // some code
      }
    }
  }

   public class Class2 {
        public void someMethod() {
          synchronized ("MyUniqueString") {
            // some code
          }
        }
      }

你的代碼似乎對我有用,即使它看起來不那么好。 但是請讓你的對象在最后進行同步。 但是,根據您的實際情況,可能會有一些考慮因素。

無論如何應該在Javadocs中清楚地說明你想要存檔的內容。

另一種方法是在FirstClass上同步,例如

synchronized (FirstClass.class) {
// do what you have to do
} 

但是, FirstClass每個synchronized方法都與上面的synchronized塊相同。 換句話說,它們也在synchronized一個對象上synchronized - 根據具體情況,可能會更好。

在其他情況下,如果您希望在db訪問或類似操作上進行同步,那么您可能更喜歡某些BlockingQueue實現。

我想你想做的就是這個。 您有兩個工作類,它們對同一個上下文對象執行某些操作。 然后,您希望鎖定上下文對象上的兩個worker類。然后,以下代碼將適用於您。

public class Worker1 {

    private final Context context;

    public Worker1(Context context) {
        this.context = context;
    }

    public void someMethod(){
        synchronized (this.context){
            // do your work here
        }
    }
}

public class Worker2 {

    private final Context context;

    public Worker2(Context context) {
        this.context = context;
    }

    public void someMethod(){
        synchronized (this.context){
            // do your work here
        }
    }
}


public class Context {

    public static void main(String[] args) {
        Context context = new Context();
        Worker1 worker1 = new Worker1(context);
        Worker2 worker2 = new Worker2(context);

        worker1.someMethod();
        worker2.someMethod();
    }
}

我認為你的方法是錯誤的,完全使用synchronized塊。 從Java 1.5開始, java.util.concurrent包就可以對同步問題進行高級控制。

例如, Semaphore類提供了一些基本工作,只需要簡單的同步:

Semaphore s = new Semaphore(1);
s.acquire();
try {
   // critical section
} finally {
   s.release();
}

即使是這個簡單的類也比同步更多,例如tryAcquire()的可能性,它會立即返回是否獲得了一個鎖,並且在鎖變為可用之前向你提供做非關鍵工作的選項。

使用這些類也可以使您的對象具有更清晰的特性。 雖然通用監視器對象可能會被誤解,但默認情況下, Semaphore與線程相關聯。

如果你進一步查看concurrent-package,你會發現更多特定的同步類,比如ReentrantReadWriteLock ,它允許定義可能有許多並發讀操作,而只有write-ops實際上與其他讀/寫同步。 您將找到一個Phaser ,它允許您同步線程,以便同步執行特定任務(與synchornized的相反方式)以及在某些情況下可能根本不需要同步的許多數據結構。

總而言之:除非您確切知道為什么或者您遇到Java 1.4,否則不要使用plain synchronized 它很難閱讀和理解,很可能你至少實現了SemaphoreLock的更高功能的一部分。

對於您的場景,我建議您編寫一個Helper類,它通過特定方法返回監視器對象。 方法名稱本身定義了鎖定對象的邏輯名稱,這有助於您的代碼可讀性。

public class LockingSupport {
    private static final LockingSupport INSTANCE = new LockingSupport();

    private Object printLock = new Object();
    // you may have different lock
    private Object galaxyLock = new Object();

    public static LockingSupport get() {
        return INSTANCE;
    }

    public Object getPrintLock() {
        return printLock;
    }

    public Object getGalaxyLock() {
        return galaxyLock;
    }
}

在要強制執行同步的方法中,您可以要求支持人員返回相應的鎖定對象,如下所示。

public static void unsafeOperation() {
    Object lock = LockingSupport.get().getPrintLock();
    synchronized (lock) {
        // perform your operation
    }
}

public void unsafeOperation2() { //notice static modifier does not matter
    Object lock = LockingSupport.get().getPrintLock();
    synchronized (lock) {
        // perform your operation
    }
}

以下是幾個優點:

  • 通過這種方法,您可以使用方法引用來查找正在使用共享鎖的所有位置。
  • 您可以編寫高級邏輯來返回不同的鎖對象(例如,基於調用者的類包,為一個包的所有類返回相同的鎖對象,但為其他包的類等返回不同的鎖對象)
  • 您可以逐步升級Lock實現以使用java.util.concurrent.locks.Lock API。 如下所示

例如(更改鎖定對象類型不會破壞現有代碼,認為使用Lock對象作為同步(鎖定)不是一個好主意)

public static void unsafeOperation2() {
    Lock lock = LockingSupport.get().getGalaxyLock();
    lock.lock();
    try {
        // perform your operation
    } finally {
        lock.unlock();
    }
}

希望它有所幫助。

首先,以下是您當前方法的問題:

  1. 鎖定對象不稱為lock或類似。 (是的......挑剔)
  2. 變量不是final 如果意外(或故意)改變了obj ,你的同步就會中斷。
  3. 變量是public 這意味着其他代碼可能會通過獲取鎖定導致問題。

我想這些影響中的一些是你批評的根源:“這對我來說似乎是不好的編碼”。

在我看來,這里有兩個基本問題:

  1. 你有一個漏洞的抽象。 以任何方式(作為公共或包私有變量或通過getter)發布“class 1”之外的鎖對象是暴露鎖定機制。 應該避免這種情況。

  2. 使用單個“全局”鎖意味着您有一個並發瓶頸。

第一個問題可以通過抽象鎖定解決。 例如:

someMethod() {
     Class1.doWithLock(() -> { /* code */ });
}

其中doWithLock()是一個靜態方法,它采用RunnableCallable或類似方法,然后使用適當的鎖運行它。 doWithLock()的實現可以根據其規范使用自己的private static final Object lock ...或其他一些鎖定機制。

第二個問題更難。 擺脫“全局鎖定”通常需要重新考慮應用程序體系結構,或者更改為不需要外部鎖定的不同數據結構。

暫無
暫無

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

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