简体   繁体   English

Java中的单例和多线程

[英]Singleton & Multithreading in Java

What is the preferred way to work with Singleton class in multithreaded environment?在多线程环境中使用 Singleton 类的首选方法是什么?

Suppose if I have 3 threads, and all of them try to access getInstance() method of singleton class at the same time -假设我有 3 个线程,并且它们都尝试同时访问单例类的getInstance()方法 -

  1. What would happen if no synchronization is maintained?如果不保持同步会发生什么?
  2. Is it good practice to use synchronized getInstance() method or use synchronized block inside getInstance() .使用synchronized getInstance()方法或在getInstance()内部使用synchronized块是一种好习惯。

Please advise if there is any other way out.请告知是否有任何其他出路。

If you're talking about threadsafe , lazy initialization of the singleton , here is a cool code pattern to use that accomplishes 100% threadsafe lazy initialization without any synchronization code :如果你在谈论线程安全单例惰性初始化,这里有一个很酷的代码模式可以使用,它可以在没有任何同步代码的情况下完成 100% 线程安全的惰性初始化

public class MySingleton {

     private static class MyWrapper {
         static MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return MyWrapper.INSTANCE;
     }
}

This will instantiate the singleton only when getInstance() is called, and it's 100% threadsafe.这将仅在调用getInstance()时实例化单例,并且它是 100% 线程安全的。 It's a classic.这是一个经典。

It works because the class loader has its own synchronization for handling static initialization of classes: You are guaranteed that all static initialization has completed before the class is used, and in this code the class is only used within the getInstance() method, so that's when the class loaded loads the inner class.它的工作原理是因为类加载器有自己的同步来处理类的静态初始化:你可以保证在使用类之前完成所有静态初始化,并且在这段代码中类只在getInstance()方法中使用,所以这是当加载的类加载内部类时。

As an aside, I look forward to the day when a @Singleton annotation exists that handles such issues.顺便说一句,我期待着存在处理此类问题的@Singleton注释的那一天。

Edited:编辑:

A particular disbeliever has claimed that the wrapper class "does nothing".一个特别的怀疑者声称包装类“什么都不做”。 Here is proof that it does matter, albeit under special circumstances.这证明它确实很重要,尽管是在特殊情况下。

The basic difference is that with the wrapper class version, the singleton instance is created when the wrapper class is loaded, which when the first call the getInstance() is made, but with the non-wrapped version - ie a simple static initialization - the instance is created when the main class is loaded.基本区别在于,对于包装类版本,单例实例是在加载包装类时创建的,这是在第一次调用getInstance()时进行的,但对于非包装版本——即简单的静态初始化——实例是在加载主类时创建的。

If you have only simple invocation of the getInstance() method, then there is almost no difference - the difference would be that all other sttic initialization would have completed before the instance is created when using the wrapped version, but this is easily dealt with by simply having the static instance variable listed last in the source.如果您只是简单地调用getInstance()方法,那么几乎没有区别 - 区别在于使用包装版本时,所有其他sttic 初始化都将在创建实例之前完成,但这很容易通过只需将静态实例变量列在源代码的最后

However, if you are loading the class by name , the story is quite different.但是,如果您按名称加载类,情况就大不相同了。 Invoking Class.forName(className) on a class cuasing static initialization to occur, so if the singleton class to be used is a property of your server, with the simple version the static instance will be created when Class.forName() is called, not when getInstance() is called.在类上调用Class.forName(className)会导致静态初始化发生,因此如果要使用的单例类是服务器的属性,则使用简单版本时将在调用Class.forName()时创建静态实例,不是在调用getInstance()时。 I admit this is a little contrived, as you need to use reflection to get the instance, but nevertheless here's some complete working code that demonstrates my contention (each of the following classes is a top-level class):我承认这有点做作,因为您需要使用反射来获取实例,但是这里有一些完整的工作代码可以证明我的观点(以下每个类都是顶级类):

public abstract class BaseSingleton {
    private long createdAt = System.currentTimeMillis();

    public String toString() {
        return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago";
    }
}

public class EagerSingleton extends BaseSingleton {

    private static final EagerSingleton INSTANCE = new EagerSingleton();

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

public class LazySingleton extends BaseSingleton {
    private static class Loader {
        static final LazySingleton INSTANCE = new LazySingleton();
    }

    public static LazySingleton getInstance() {
        return Loader.INSTANCE;
    }
}

And the main:和主要的:

public static void main(String[] args) throws Exception {
    // Load the class - assume the name comes from a system property etc
    Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
    Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.EagerSingleton");

    Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance()

    // Invoke the getInstace method on the class
    BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz);
    BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz);

    System.out.println(lazySingleton);
    System.out.println(eagerSingleton);
}

Output:输出:

LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago

As you can see, the non-wrapped, simple implementation is created when Class.forName() is called, which may be before the static initialization is ready to be executed.如您所见,非包装的简单实现是在调用Class.forName()时创建的,这可能是静态初始化准备好执行之前。

The task is non-trivial in theory, given that you want to make it truly thread safe.鉴于您希望使其真正线程安全,因此该任务在理论上并非易事。

A very nice paper on the matter is found@ IBM@IBM找到了一篇关于此事的非常好的论文

Just getting the singleton does not need any sync, since it's just a read.只获取单例不需要任何同步,因为它只是一个读取。 So, just synchronize the setting of the Sync would do.所以,只需同步Sync 的设置即可。 Unless two treads try to create the singleton at start up at the same time, then you need to make sure check if the instance is set twice (one outside and one inside the sync) to avoid resetting the instance in a worst case scenario.除非两个线程同时尝试在启动时创建单例,否则您需要确保检查实例是否设置了两次(一次在同步之外,一次在同步内)以避免在最坏的情况下重置实例。

Then you might need to take into account how JIT (Just-in-time) compilers handle out-of-order writes.那么您可能需要考虑 JIT(即时)编译器如何处理乱序写入。 This code will be somewhat near the solution, although won't be 100% thread safe anyway:这段代码将有点接近解决方案,尽管无论如何都不是 100% 线程安全的:

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

So, you should perhaps resort to something less lazy:所以,你也许应该求助于一些不那么懒惰的东西:

class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() { }

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

Or, a bit more bloated, but a more flexible way is to avoid using static singletons and use an injection framework such as Spring to manage instantiation of "singleton-ish" objects (and you can configure lazy initialization).或者,有点臃肿,但更灵活的方法是避免使用静态单例,并使用诸如Spring 之类的注入框架来管理“单例”对象的实例化(您可以配置惰性初始化)。

You need synchronization inside getInstance only if you initialize your singleton lazily.仅当您懒惰地初始化单例时,才需要在getInstance内部进行同步。 If you could create an instance before the threads are started, you can drop synchronization in the getter, because the reference becomes immutable.如果您可以在线程启动之前创建一个实例,则可以在 getter 中删除同步,因为引用变得不可变。 Of course if the singleton object itself is mutable, you would need to synchronize its methods which access information that can be changed concurrently.当然,如果单例对象本身是可变的,则需要同步其访问可同时更改的信息的方法。

This question really depends on how and when your instance is created.这个问题实际上取决于创建实例的方式和时间。 If your getInstance method lazily initializes:如果您的getInstance方法延迟初始化:

if(instance == null){
  instance = new Instance();
}

return instance

Then you must synchronize or you could end up with multiple instances.然后你必须同步,否则你可能会得到多个实例。 This problem is usually treated in talks on Double Checked Locking .这个问题通常在Double Checked Locking的会谈中处理。

Otherwise if you create a static instance up front否则,如果您预先创建一个静态实例

private static Instance INSTANCE = new Instance();

then no synchronization of the getInstance() method is necessary.那么不需要同步getInstance()方法。

The best way as described in effective java is:有效 java 中描述的最佳方法是:

public class Singelton {

    private static final Singelton singleObject = new Singelton();

    public Singelton getInstance(){
        return singleObject;
    }
}

No need of synchronization.不需要同步。

Nobody uses Enums as suggested in Effective Java ?没有人按照Effective Java中的建议使用 Enums 吗?

If you are sure that your java runtime is using the new JMM (Java memory model, probably newer than 5.0) , double check lock is just fine, but add a volatile in front of instance.如果您确定您的 java 运行时使用的是新的 JMM(Java 内存模型,可能比 5.0 更新) ,仔细检查锁就可以了,但是在实例前面添加一个 volatile。 Otherwise, you'd better use static internal class as Bohemian said, or Enum in 'Effective Java' as Florian Salihovic said.否则,您最好像 Bohemian 所说的那样使用静态内部类,或者像 Florian Salihovic 所说的“Effective Java”中的 Enum。

For simplicity, I think using enum class is a better way.为简单起见,我认为使用枚举类是更好的方法。 We don't need to do any synchronization.我们不需要做任何同步。 Java by construct, always ensure that there is only one constant created, no matter how many threads are trying to access it. Java 通过构造,始终确保只创建一个常量,无论有多少线程试图访问它。

FYI, In some case you need to swap out singleton with other implementation.仅供参考,在某些情况下,您需要将单例替换为其他实现。 Then we need to modify class, which is violation of Open Close principal.Problem with singleton is, you can't extend the class because of having private constructor.然后我们需要修改类,这违反了 Open Close 原则。单例的问题是,你不能扩展类,因为有私有构造函数。 So, it's a better practice that client is talking via interface.因此,客户通过界面交谈是一种更好的做法。

Implementation of Singleton with enum class and Interface:使用枚举类和接口实现单例:

Client.java客户端.java

public class Client{
    public static void main(String args[]){
        SingletonIface instance = EnumSingleton.INSTANCE;
        instance.operationOnInstance("1");
    }
}

SingletonIface.java SingletonIface.java

public interface SingletonIface {
    public void operationOnInstance(String newState);
}

EnumSingleton.java枚举单例.java

public enum EnumSingleton implements SingletonIface{
INSTANCE;

@Override
public void operationOnInstance(String newState) {
    System.out.println("I am Enum based Singleton");
    }
}

The Answer is already accepted here, But i would like to share the test to answer your 1st question.此处已接受答案,但我想分享测试以回答您的第一个问题。

What would happen if no synchronization is maintained?如果不保持同步会发生什么?

Here is the SingletonTest class which will be completely disaster when you run in multi Threaded Environment.这是SingletonTest类,当您在多线程环境中运行时,它将完全是灾难。

/**
 * @author MILAN
 */
public class SingletonTest
{
    private static final int        PROCESSOR_COUNT = Runtime.getRuntime().availableProcessors();
    private static final Thread[]   THREADS         = new Thread[PROCESSOR_COUNT];
    private static int              instancesCount  = 0;
    private static SingletonTest    instance        = null;

    /**
     * private constructor to prevent Creation of Object from Outside of the
     * This class.
     */
    private SingletonTest()
    {
    }

    /**
     * return the instance only if it does not exist
     */
    public static SingletonTest getInstance()
    {
        if (instance == null)
        {
            instancesCount++;
            instance = new SingletonTest();
        }
        return instance;
    }

    /**
     * reset instancesCount and instance.
     */
    private static void reset()
    {
        instancesCount = 0;
        instance = null;
    }

    /**
     * validate system to run the test
     */
    private static void validate()
    {
        if (SingletonTest.PROCESSOR_COUNT < 2)
        {
            System.out.print("PROCESSOR_COUNT Must be >= 2 to Run the test.");
            System.exit(0);
        }
    }

    public static void main(String... args)
    {
        validate();
        System.out.printf("Summary :: PROCESSOR_COUNT %s, Running Test with %s of Threads. %n", PROCESSOR_COUNT, PROCESSOR_COUNT);
        
        long currentMili = System.currentTimeMillis();
        int testCount = 0;
        do
        {
            reset();

            for (int i = 0; i < PROCESSOR_COUNT; i++)
                THREADS[i] = new Thread(SingletonTest::getInstance);

            for (int i = 0; i < PROCESSOR_COUNT; i++)
                THREADS[i].start();

            for (int i = 0; i < PROCESSOR_COUNT; i++)
                try
                {
                    THREADS[i].join();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            testCount++;
        }
        while (instancesCount <= 1 && testCount < Integer.MAX_VALUE);
        
        System.out.printf("Singleton Pattern is broken after %d try. %nNumber of instances count is %d. %nTest duration %dms", testCount, instancesCount, System.currentTimeMillis() - currentMili);
    }
}

Output of the program is clearly shows that you need handle this using getInstance as synchronized or add synchronized lock enclosing new SingletonTest.该程序的输出清楚地表明您需要使用 getInstance 作为synchronized来处理此问题,或者添加包含新 SingletonTest 的synchronized锁。

Summary :: PROCESSOR_COUNT 32, Running Test with 32 of Threads. 
Singleton Pattern is broken after 133 try. 
Number of instance count is 30. 
Test duration 500ms

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

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