简体   繁体   English

单例类中的方法是线程安全的吗?

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

I'm designing a custom logging framework for our application. 我正在为我们的应用程序设计一个自定义日志框架。

I'm reading Patterns For Logging Diagnostic Messages , which is being used in http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html 我正在阅读http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html中使用的记录诊断消息的模式。

On page 3 it says: 在第3页上说:

Because it is a Singleton, it easily preserves the order of the messages. 因为它是单例,所以可以轻松保留消息的顺序。

I think, for example, given Singleton class S, if a class B is trying to acquire the instance of S while a class A already got the instance S, then B cannot acquire the instance of S since A already got the instance of S. 我认为,例如,给定Singleton类S,如果类B试图获取S的实例,而类A已经获得了实例S,则B无法获取S的实例,因为A已经获得了S的实例。

That's why the order of message is preserved based on my understanding. 这就是为什么根据我的理解保留消息顺序的原因。

  1. Is my understanding correct? 我的理解正确吗?

  2. How the class B knows that class A is done with the class S and no longer needs it so that B can acquire S? B类如何知道A类是由S类完成的,而不再需要它,以便B可以获取S?

  3. if my understanding is correct and if Singleton class S has some methods: test1() and test2() as you see below. 如果我的理解是正确的,并且Singleton类S是否具有某些方法:如下所示,则为test1()和test2()。

Are test1() and test2() are thread-safe? test1()和test2()是线程安全的吗?

These methods will be called outside of class S, something like 这些方法将在类S外部调用,例如

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

in class A or class B for example. 例如在A类或B类中。

Which means when class A and class B are trying to write some data in a log file by calling test1(), this data will be written in the order of acquiring the instance of S? 这意味着当类A和类B通过调用test1()尝试在日志文件中写入一些数据时,将按照获取S实例的顺序来写入这些数据?

If not, to make methods thread-safe in a Singleton class S, should I also use synchronized keyword on methods test1() and test2() or lock for these methods? 如果不是这样,做一个单身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
    }
}

This is definitely not thread-safe. 这绝对不是线程安全的。 Suppose for instance I had two threads T1, and T2, and S had property foo. 例如,假设我有两个线程T1和T2,而S具有属性foo。 Suppose T1 and T2 are modifying the value of foo, and then using the value of foo to perform some other operation. 假设T1和T2正在修改foo的值,然后使用foo的值执行其他操作。

Then, I could presumably have T1 access S.getInstance, check the getInstance is not set, and at the same time, T2 can access S.getInstance and see that the instance is not set. 然后,大概可以让T1访问S.getInstance,检查未设置getInstance,同时T2可以访问S.getInstance,并查看未设置实例。 T1, could then potentially set the instance, but since T2 had also at the same time detected that the instance was not set, would also set the instance for S. Therefore, the value of S.instance, is going to actually be the one set by T2. 然后,T1可能会设置实例,但由于T2同时也检测到未设置实例,因此也会为S设置实例。因此,S.instance的值实际上将是该实例。由T2设置。 In otherwords, there is a race condition between T1 and T2 to see who can set the instance of S first. 换句话说,在T1和T2之间存在一个竞争条件,以查看谁可以首先设置S的实例。

To make this synchronous, you should definitely have the getInstance method be synchronized so only one thread could be acting on it at once. 为使此同步,您绝对应该使getInstance方法同步,以便一次只能有一个线程对其进行操作。 Also, you should probably make the instance of S volatile so as to ensure that any thread that is accessing the instance of S is always going to be working with the "latest" copy. 此外,您可能应该使S的实例可变,以确保访问S的实例的任何线程始终将与“最新”副本一起使用。 (because presumably one thread could be doing some other read operation on that instance while it's being modified). (因为大概一个线程在修改实例时可能对该实例执行其他读取操作)。

ie something like this: 即是这样的:

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
    }
}

Also, here's a good link on why you should use volatile: 此外,以下是有关为什么应使用volatile的良好链接:

What is the point of making the singleton instance volatile while using double lock? 使用双重锁定时使单例实例具有可变性有什么意义?

The code in your example is not thread safe. 您示例中的代码不是线程安全的。 If two concurrent threads try to get the singleton object at the same time, there is a chance each thread creates an object. 如果两个并发线程试图同时获取单例对象,则每个线程都有机会创建一个对象。 You should use synchronized keyword on the getInstance method to avoid this behavior. 您应该使用synchronized的关键字getInstance方法来避免此行为。

You also have to mark as synchronized every instance method (not static) on the singleton class that access data from the object (non-static properties). 您还必须将访问对象数据(非静态属性)的单例类上的每个实例方法(非静态)标记为已synchronized This way, two different methods will never run concurrently, preventing the data from being messed up. 这样,两种不同的方法将永远不会同时运行,从而防止数据混乱。

In your example, you don't need to use synchronized on test1 and test2 as long as the methods they call from the instance are all synchronized . 在你的榜样,你没有必要使用synchronizedtest1test2 ,只要他们从实例调用的方法都是synchronized

Your S class should probably look like this 您的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();
        // ...
    }
}

First, your code sample above is not a safe singleton. 首先,上面的代码示例不是安全的单例。 There are possibility of race conditions at static method getInstance . 静态方法getInstance可能存在竞争条件。 If 2 or more threads run if(instance==null) at the same time, more than one S instances will be constructed. 如果同时运行2个或更多线程if(instance==null) ,则将构造一个以上的S实例。 To correct this use eager-initialization by providing a static final field of your class instance. 要纠正此问题,请通过提供类实例的static final字段来使用eager-initialization

Example:- 例:-

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
     }
}

Even better since JDK 5, use enum type to represent safe singleton. 自JDK 5起,甚至更好。使用枚举类型表示安全的单例。 It also provide guards against serialization attack for free. 它还免费提供了防止序列化攻击的防护措施。

Example:- 例:-

public enum Singleton {

    S;

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

Secondly, if doSomething does not modify S instance state or it is a stateless object, then it is thread-safe. 其次,如果doSomething不修改S实例状态或它是无状态对象,则它是线程安全的。 Otherwise have to provide synchronization guards to preserve its state correctness in multi-threaded environment. 否则,必须提供同步保护措施以在多线程环境中保留其状态正确性。

Note : In the past, many naive implementations of double locking idiom to solve lazy-loading singleton without synchronization were fraught with perils. 注意 :过去,双锁惯用语的许多幼稚实现都解决了没有同步的延迟加载单例的问题。 Refer to good article below signed by Doug Lee, Joshua Bloch & et al. 请参阅以下Doug Lee,Joshua Bloch等人签名的好文章。 for further read. 供进一步阅读。

The "Double-Checked Locking is Broken" Declaration “双重检查锁定已损坏”声明

To answer your questions: 要回答您的问题:

  1. No, your understanding is incorrect. 不,您的理解不正确。
  2. By using some sort of locking mechanism, such as a Semaphore . 通过使用某种锁定机制,例如Semaphore
  3. That depends entirely on what's inside those methods. 这完全取决于这些方法内部的内容。 If they only use variables that are local to the method (ie those that are on the stack and not in the heap) that the method is probably thread-safe. 如果它们仅使用方法局部变量(即那些在堆栈上而不在堆中的变量),则该方法可能是线程安全的。 If you need to access to variables that are local to the class, such as a log file, you again need a locking mechanism. 如果您需要访问类本地变量,例如日志文件,则再次需要一种锁定机制。

Just to add, you cannot have a static getInstance method and expect it to be thread-safe. 只是要添加,您不能具有静态的getInstance方法并且期望它是线程安全的。 The best way to accomplish this is to have code as follows: 最好的方法是编写如下代码:

private final static S INSTANCE = new S();

This will only be instantiated the moment you first access it. 这只会在您首次访问它时实例化。

This is not thread safe. 这不是线程安全的。 Imagine two competing threads trying to get an instance of S, and both checking at the same time if the instance is null. 想象一下两个竞争的线程试图获取S的实例,并同时检查该实例是否为null。 In order to lock this for thread safety you will need to use the synchronized keyword. 为了锁定此线程以确保线程安全,您将需要使用synced关键字。 Try this: 尝试这个:

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
    }
}

Another option, to avoid the double-checked locking above would be to synchronize the getInstance method like so (although with and added performance hit): 避免上述双重检查锁定的另一种方法是像这样同步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