简体   繁体   English

有关单例类和线程的问题

[英]question about singleton classes and threads

I'm trying to learn about singleton classes and how they can be used in an application to keep it thread safe. 我正在尝试了解单例类,以及如何在应用程序中使用它们以确保其线程安全。 Let's suppose you have an singleton class called IndexUpdater whose reference is obtained as follows: 假设您有一个称为IndexUpdater的单例类,其引用如下获得:

 public static synchronized IndexUpdater getIndexUpdater() {
    if (ref == null)
        // it's ok, we can call this constructor
        ref = new IndexUpdater();
    return ref;
}

private static IndexUpdater ref;

Let's suppose there are other methods in the class that do the actual work (update indicies, etc.). 假设类中还有其他方法可以完成实际工作(更新索引等)。 What I'm trying to understand is how accessing and using the singleton would work with two threads. 我想了解的是如何使用两个线程来访问和使用单例。 Let's suppose in time 1, thread 1 gets a reference to the class, through a call like this IndexUpdater iu = IndexUpdater.getIndexUpdater(); 假设在时间1中,线程1通过像这样的调用获得对类的引用:IndexUpdater iu = IndexUpdater.getIndexUpdater(); Then, in time 2, using reference iu, a method within the class is called iu.updateIndex by thread 1. What would happen in time 2, a second thread tries to get a reference to the class. 然后,在时间2中,使用引用iu,线程1将类中的方法称为iu.updateIndex。在时间2中会发生什么,第二个线程尝试获取对该类的引用。 Could it do this and also access methods within the singleton or would it be prevented as long as the first thread has an active reference to the class. 它可以做到这一点,也可以访问单例内的方法吗?或者,只要第一个线程对该类有有效的引用,就可以阻止它。 I'm assuming the latter (or else how would this work?) but I'd like to make sure before I implement. 我假设使用后者(否则它将如何工作?),但我想在实施之前先确定一下。

Thank you, 谢谢,

Elliott 艾略特

Your assumption is wrong. 您的假设是错误的。 Synchronizing getIndexUpdater() only prevents more than one instance being created by different threads calling getIndexUpdater() at (almost) the same time. 同步getIndexUpdater()仅防止(几乎)同时调用getIndexUpdater()的不同线程创建多个实例。

Without synchronization the following could happen: Thread one calls getIndexUpdater(). 没有同步,可能会发生以下情况:线程一调用getIndexUpdater()。 ref is null. ref为空。 Thread 2 calls getIndexUpdater(). 线程2调用getIndexUpdater()。 ref is still null. ref仍然为null。 Outcome: ref is instantiated twice. 结果:ref被实例化两次。

Since getIndexUpdater() is a synchronized method, it only prevents threads from accessing this method (or any method protected by the same synchronizer) simultaneously. 由于getIndexUpdater()是同步方法,因此它仅防止线程同时访问此方法(或由同一同步器保护的任何方法)。 So it could be a problem if other threads are accessing the object's methods at the same time. 因此,如果其他线程同时访问该对象的方法,则可能会出现问题。 Just keep in mind that if a thread is running a synchronized method, all other threads trying to run any synchronized methods on the same object are blocked. 请记住,如果线程正在运行同步方法,则所有其他试图在同一对象上运行任何同步方法的线程都将被阻止。

More info on: http://download.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html 有关更多信息: http : //download.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

You are conflating the instantiation of a singleton object with its use. 您正在将单例对象的实例化与其使用混淆。 Synchronizing the creation of a singleton object does not guarantee that the singleton class itself is thread-safe. 同步创建单例对象并不能保证单例类本身是线程安全的。 Here is a simple example: 这是一个简单的示例:

public class UnsafeSingleton {
    private static UnsafeSingleton singletonRef;
    private Queue<Object> objects = new LinkedList<Object>();

    public static synchronized UnsafeSingleton getInstance() {
        if (singletonRef == null) {
            singletonRef = new UnsafeSingleton();
        }

        return singletonRef;
    }

    public void put(Object o) {
        objects.add(o);
    }

    public Object get() {
        return objects.remove(o);
    }
}

Two threads calling getInstance are guaranteed to get the same instance of UnsafeSingleton because synchronizing this method guarantees that singletonRef will only be set once. 保证两个调用getInstance线程都可以获取UnsafeSingleton的相同实例,因为同步此方法可以保证singletonRef仅被设置一次。 However, the instance that is returned is not thread safe , because (in this example) LinkedList is not a thread-safe queue. 但是,返回的实例不是线程安全的 ,因为(在此示例中) LinkedList不是线程安全的队列。 Two threads modifying this queue may result in unexpected behavior. 两个线程修改此队列可能会导致意外行为。 Additional steps have to be taken to ensure that the singleton itself is thread-safe, not just its instantiation. 必须采取其他步骤来确保单例本身是线程安全的,而不仅仅是其实例化。 (In this example, the queue implementation could be replaced with a LinkedBlockingQueue , for example, or the get and put methods could be marked synchronized .) (例如,在此示例中,可以用LinkedBlockingQueue替换队列实现,或者可以将getput方法标记为synchronized 。)

Then, in time 2, using reference iu, a method within the class is called iu.updateIndex by thread 1. What would happen in time 2, a second thread tries to get a reference to the class. 然后,在时间2中,使用引用iu,线程1将类中的方法称为iu.updateIndex。在时间2中会发生什么,第二个线程尝试获取对该类的引用。 Could it do this and also access methods within the singleton ...? 它可以做到这一点,并且可以访问单例内的方法吗?

The answer is yes. 答案是肯定的。 Your assumption on how references are obtained is wrong. 您关于如何获得引用的假设是错误的。 The second thread can obtain a reference to the Singleton. 第二个线程可以获得对Singleton的引用。 The Singleton pattern is most commonly used as a sort of pseudo-global state. Singleton模式最常用作一种伪全局状态。 As we all know, global state is generally very difficult to deal with when multiple entities are using it. 众所周知,当多个实体使用全局状态时,通常很难处理它。 In order to make your singleton thread safe you will need to use appropriate safety mechanisms such as using atomic wrapper classes like AtomicInteger or AtomicReference (etc...) or using synchronize (or Lock ) to protect critical areas of code from being accessed simultaneously. 为了使您的单例线程安全,您将需要使用适当的安全机制,例如使用原子包装器类(例如AtomicIntegerAtomicReference等)或使用synchronize (或Lock )来保护关键代码区域免于同时访问。

The safest is to use the enum-singleton. 最安全的方法是使用枚举。

public enum Singleton {
  INSTANCE;
  public String method1() {
    ...
  }
  public int method2() {
    ...
  }
}

Thread-safe, serializable, lazy-loaded, etc. Only advantages ! 线程安全,可序列化,延迟加载等。只有优点!

When a second thread tries to invoke getIndexUpdater() method, it will try to obtain a so called lock , created for you when you used synchronized keyword. 当第二个线程尝试调用getIndexUpdater()方法时,它将尝试获取所谓的lock ,它是在您使用synchronized关键字时为您创建的。 But since some other thread is already inside the method, it obtained the lock earlier and others (like the second thread) must wait for it. 但是由于方法中已经有其他线程,因此它较早获得了锁,而其他线程(如第二个线程)必须等待它。

When the first thread will finish its work, it will release the lock and the second thread will immediately take it and enter the method. 当第一个线程完成其工作时,它将释放锁,第二个线程将立即获取该锁并进入方法。 To sum up, using synchronized always allows only one thread to enter guarded block - very restrictive access. 综上所述,使用synchronized总是只允许一个线程进入受保护的块-非常严格的访问。

The static synchronized guarantees that only one thread can be in this method at once and any other thread attempting to access this method (or any other static synchronized method in this class) will have to wait for it to complete. 静态同步保证了一次只能有一个线程进入该方法,并且任何其他尝试访问此方法的线程(或此类中的任何其他静态同步方法)都必须等待它完成。

IMHO the simplest way to implement a singleton is to have a enum with one value 恕我直言,实现单例的最简单方法是让枚举具有一个值

enum Singleton {
    INSTANCE
}

This is thread safe and only creates the INSTANCE when the class is accessed. 这是线程安全的,并且仅在访问该类时创建INSTANCE。

As soon as your synchronized getter method will return the IndexUpdater instance (whether it was just created or already existed doesn't matter), it is free to be called from another thread. 一旦您的同步getter方法将返回IndexUpdater实例(无论它是刚刚创建还是已经存在都没有关系),就可以从另一个线程中调用它。 You should make sure your IndexUpdater is thread safe so it can be called from multiple threads at a time, or you should create an instance per thread so they won't be shared. 您应该确保IndexUpdater是线程安全的,以便可以一次从多个线程中调用它,或者应该为每个线程创建一个实例,以使它们不会被共享。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM