簡體   English   中英

單例上的同步訪問

[英]Synchronized access on a singleton

我有一個關於單例同步的問題。 假設我有一個單例模式,如下所示:

public class Singleton {

    private final Object obj;

    private static Singleton instance;

    private Singleton(Object obj) {
        this.obj = obj;
    }

    public static synchronized Singleton getInstance(Object obj) {
        if (instance == null) {
            instance = new Singleton(obj);
        }
        return instance;
    }

    public synchronized void methodA() {
        // ...
    }

    public synchronized void methodB() {
        // ...
    }
}

我想同步對其的訪問。 我有兩個假設,需要驗證。

  1. 假設:由於所有方法(包括初始化程序)都已同步,因此對該Singleton的每次訪問都是線程安全和同步的。

  2. 假設:當我想要確保的是要調用一個線程methodA()然后立即methodB()沒有其他線程調用單的方法,是正確的在這樣的情況下同步?

Singleton singleton = Singleton.getInstance(obj);

synchronized (singleton) {
    singleton.methodA();
    singleton.methodB();
}

說明:

1.假設是否正確,因為在非靜態方法上同步是在對象本身上同步,並且由於它始終是同一對象,所以訪問是同步的? 並且對getInstance(obj)的調用是否在類本身上同步?

2.假設是否正確,因為每個線程都使用getInstance(obj)獲取相同的對象,因此同步是正確的,因為另一個線程將等待直到退出同步塊( ...methodA(); methodB(); )?

假設我理解正確,您的假設是正確的。 我只想指出,在大多數情況下,您可以使用更簡單的單例模式:

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

該字段將以線程安全的方式在第一個靜態引用(例如getInstance() )上初始化,而無需顯式同步。

同樣, instance應該是final

您的第一個假設是正確的,但是,在專用鎖對象上進行synchronized而不是使用synchronized方法是一個好習慣。 這樣做可以將並發問題排除在API之外,並允許您稍后使用java.util.concurrent包替換鎖,以實現更有效的實現。 您還可以防止死鎖,因為其他對象無法獲取相同的鎖對象。

public class Example {

  // leaving out the singleton aspect here

  // consider java.util.concurrent.locks instead
  private Object lock = new Object();

  public synchronized void methodA() {
    synchronized(lock) {
      // ...
    }
  }

  public synchronized void methodB() {
    synchronized(lock) {
      // ...
    }
  }
}

客戶端仍然可以在實例上同步:

synchronized(instance) {
  instance.methodA();
  instance.methodB();
}

例如,查看java.util.Collections類中SynchronizedCollection的實現,該類也使用內部鎖定對象。

與單例同步有關的一個問題:如果您的應用程序中有多個並發線程(例如,在Web應用程序中),則由於所有線程都必須互相等待,因此此中央鎖很快就會成為性能瓶頸。

兩者都是對的。

最好的測試方法(測試解決方案總是好的,而不是依賴於假設)將是在調用方法之間等待一段時間,然后從不同的線程調用methodA,然后檢查執行順序。 例如:

public synchronized void methodA() {
    println("methodA");
}

public synchronized void methodB() {
    println("methodB");
}

然后,在測試用例或主要方法中:

new Thread(){
    public void run(){
           Singleton singleton = Singleton.getInstance();

           synchronized (singleton) {
           singleton.methodA();
           singleton.wait(5000L);//this gives us 5 seconds to call it from different thread
           singleton.methodB();
       } 
    }
}.start();

new Thread(){
    public void run(){
           Singleton.getInstance().methodA();
       } 
    }
}.start();

如您所知,synchronized關鍵字用於獲取對象上的鎖。 如果要在執行一種方法之后調用自願方法。 然后,我們可以像下面的代碼一樣從同步方法中調用該方法,但是線程要等到它從第一個同步塊中出來后才會釋放。

   public synchronized void method1A() {
       // ......
    method1B();
   }

   public synchronized void method1B() {
       // ...
   }

在這里,線程將獲取最后輸入到同步方法1A中的鎖,它將調用另一個同步方法1B。 線程將在method1A執行完成后釋放鎖。

你們兩個假設都是正確的。 但是,對於創建Singleton,我將建議以下方法是100%線程安全的,並且沒有同步問題,因為它利用了Java的類加載機制。

像此類一樣,Provider將僅被加載一次,因此將僅存在Network類的一個實例,並且由於類不會被加載兩次,因此沒有兩個線程可以在同一JVM中創建2個實例。

public class Network {
    private Network(){

    }

    private static class Provider {
        static final Network INSTANCE = new Network();
    }

    public static Network getInstance() {
        return Provider.INSTANCE;
    }
    //More code...
}

線程安全單例樣本;

public class Singleton {

        private static Singleton instance;

        private Singleton(){

        }

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

暫無
暫無

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

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