簡體   English   中英

單例類中的方法是線程安全的嗎?

[英]methods in a singleton class are thread-safe?

我正在為我們的應用程序設計一個自定義日志框架。

我正在閱讀http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html中使用的記錄診斷消息的模式。

在第3頁上說:

因為它是單例,所以可以輕松保留消息的順序。

我認為,例如,給定Singleton類S,如果類B試圖獲取S的實例,而類A已經獲得了實例S,則B無法獲取S的實例,因為A已經獲得了S的實例。

這就是為什么根據我的理解保留消息順序的原因。

  1. 我的理解正確嗎?

  2. B類如何知道A類是由S類完成的,而不再需要它,以便B可以獲取S?

  3. 如果我的理解是正確的,並且Singleton類S是否具有某些方法:如下所示,則為test1()和test2()。

test1()和test2()是線程安全的嗎?

這些方法將在類S外部調用,例如

S.getInstance().test1("message")

例如在A類或B類中。

這意味着當類A和類B通過調用test1()嘗試在日志文件中寫入一些數據時,將按照獲取S實例的順序來寫入這些數據?

如果不是這樣,做一個單身S類方法是線程安全的,我也應該使用synchronized關鍵字的方法TEST1()和TEST2()或lock這些方法?

public class S {                  // singleton class S

    private static S instance;

    private S(){}

    public static S getInstance(){
        if(instance == null){
            instance = new S();
        }
        return instance;
    }

    public static void test1(String log) {
       // writing some data to a log file
    }

    public static void test2(String log) {
       // writing some data to a log file
    }
}

這絕對不是線程安全的。 例如,假設我有兩個線程T1和T2,而S具有屬性foo。 假設T1和T2正在修改foo的值,然后使用foo的值執行其他操作。

然后,大概可以讓T1訪問S.getInstance,檢查未設置getInstance,同時T2可以訪問S.getInstance,並查看未設置實例。 然后,T1可能會設置實例,但由於T2同時也檢測到未設置實例,因此也會為S設置實例。因此,S.instance的值實際上將是該實例。由T2設置。 換句話說,在T1和T2之間存在一個競爭條件,以查看誰可以首先設置S的實例。

為使此同步,您絕對應該使getInstance方法同步,以便一次只能有一個線程對其進行操作。 此外,您可能應該使S的實例可變,以確保訪問S的實例的任何線程始終將與“最新”副本一起使用。 (因為大概一個線程在修改實例時可能對該實例執行其他讀取操作)。

即是這樣的:

public class S {                  // singleton class S

    private volatile static S instance;

    private S(){}

    public synchronized static S getInstance(){
        if(instance == null){
            instance = new S();
        }
        return instance;
    }

    public static void test1(String log) {
       // writing some data to a log file
    }

    public static void test2(String log) {
       // writing some data to a log file
    }
}

此外,以下是有關為什么應使用volatile的良好鏈接:

使用雙重鎖定時使單例實例具有可變性有什么意義?

您示例中的代碼不是線程安全的。 如果兩個並發線程試圖同時獲取單例對象,則每個線程都有機會創建一個對象。 您應該使用synchronized的關鍵字getInstance方法來避免此行為。

您還必須將訪問對象數據(非靜態屬性)的單例類上的每個實例方法(非靜態)標記為已synchronized 這樣,兩種不同的方法將永遠不會同時運行,從而防止數據混亂。

在你的榜樣,你沒有必要使用synchronizedtest1test2 ,只要他們從實例調用的方法都是synchronized

您的S類應該看起來像這樣

public class S {

    private static S instance;

    private S() {}

    public synchronized static S getInstance() {
        if (instance == null) {
            instance = new S();
        }
        return instance;
    }

    public synchronized doSomething() {
        // Code that uses data from instance
    }

    public static void test1(String log) {
        // ...
        S.getInstance().doSomthing();
        // ...
    }

    public static void test2(String log) {
        // ...
        S.getInstance().doSomthing();
        // ...
    }
}

首先,上面的代碼示例不是安全的單例。 靜態方法getInstance可能存在競爭條件。 如果同時運行2個或更多線程if(instance==null) ,則將構造一個以上的S實例。 要糾正此問題,請通過提供類實例的static final字段來使用eager-initialization

例:-

public class S{

     private static final S INSTANCE = new S();

     private S(){}

     public static S getInstance(){
         return INSTANCE;
     }

     public void doSomething(){
         //the rest of the code
     }
}

自JDK 5起,甚至更好。使用枚舉類型表示安全的單例。 它還免費提供了防止序列化攻擊的防護措施。

例:-

public enum Singleton {

    S;

    public void doSomething(){/*...*/}
}

其次,如果doSomething不修改S實例狀態或它是無狀態對象,則它是線程安全的。 否則,必須提供同步保護措施以在多線程環境中保留其狀態正確性。

注意 :過去,雙鎖慣用語的許多幼稚實現都解決了沒有同步的延遲加載單例的問題。 請參閱以下Doug Lee,Joshua Bloch等人簽名的好文章。 供進一步閱讀。

“雙重檢查鎖定已損壞”聲明

要回答您的問題:

  1. 不,您的理解不正確。
  2. 通過使用某種鎖定機制,例如Semaphore
  3. 這完全取決於這些方法內部的內容。 如果它們僅使用方法局部變量(即那些在堆棧上而不在堆中的變量),則該方法可能是線程安全的。 如果您需要訪問類本地變量,例如日志文件,則再次需要一種鎖定機制。

只是要添加,您不能具有靜態的getInstance方法並且期望它是線程安全的。 最好的方法是編寫如下代碼:

private final static S INSTANCE = new S();

這只會在您首次訪問它時實例化。

這不是線程安全的。 想象一下兩個競爭的線程試圖獲取S的實例,並同時檢查該實例是否為null。 為了鎖定此線程以確保線程安全,您將需要使用synced關鍵字。 嘗試這個:

public class S {                  // singleton class S

    private static S instance;

    private S(){}

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

        }
        return instance;
    }

    public static void test1(String log) {
        // writing some data to a log file
    }

    public static void test2(String log) {
        // writing some data to a log file
    }
}

避免上述雙重檢查鎖定的另一種方法是像這樣同步getInstance方法(盡管會增加性能影響):

public static synchronized S getInstance()
    {
        if (instance == null)
        {
            instance = new S();
        }

        return instance;
    }

暫無
暫無

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

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