繁体   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