Detailed Singleton pattern

来源:互联网 发布:oracle sql执行顺序 编辑:程序博客网 时间:2024/06/13 09:23

Resume

In software development, multitudes of people use Singleton pattern to restrict the number of instance. It is very useful when we want a class have only one instance.

However, some people do not pay enough attention to this pattern so that in some situations, it will cause some problems.


Different designs

Lazy initialization

Attention, you should not use this method in your project. Because this method of initialization is not thread safe. That is to say, in some situations, this will cause exception.

public class Singleton{    private Singleton INSTANCE = null;    public Singleton getInstance(){        if(INSTANCE == null)            INSTANCE = new Singleton();        return INSTANCE;    }}

If you have more than 1 thread, it can cause exception. Because in some situations, different threads are trying to get an instance of Singleton at the same time. So that we may have 2 instance in 2 threads! What a fatal exception! So NEVER try to use this method.

Synchronized invoke

After knowing the problem caused by multi-thread, some people may say that we should just use synchronized method to insure only one instance will be create.

public class Singleton{    private Singleton INSTANCE;    public synchronized Singleton getInstance(){        if(INSTANCE == null)            INSTANCE = new Singleton();        return INSTANCE;    }}

Double-checked locking

The codes above have fixed a problem, but it will also cause a performance problem. As the cost of synchronization is vastly big, we should try to minimize the use of synchronization. So we can have Double-checked locking which can help us improve the performance of our program.

public final class Singleton {    private static Singleton INSTANCE;    private Singleton () { }    public static Singleton getInstance() {        if (INSTANCE == null ) {            synchronized (Singleton.class) {                if (INSTANCE== null)//We need to check again if INSTANCE have been initialized because the verification before was in a non-thread safe condition.                    INSTANCE= new Singleton();            }        }        return INSTANCE;    }}

Well, in fact, in Java, the code above may not work. According to this page (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html), this code may cause exception in the presence of either optimizing compilers or shared memory multiprocessors. Especially in some languages which haven’t specified synchronization library or compiler such as C++.
(Reasons that this code doesn’t work are written in the link above, at this moment we won’t provide details)

Double-checked locking using volatile

In JDK5 or later, Sun has extended the semantics for volatile so that the compiler will never try to cache a volatile variable. It will always read variable from main memory and not from a cache ,register or a shared memory.
Hence, we can use volatile to avoid the exception caused by multi-thread.

public class Singleton{    private static volatile Singleton INSTANCE = null;    private Singleton(){ }    public static Singleton getInstance(){        if(INSTANCE == null){            synchronized(Singleton.class){                if(INSTANCE == null)                    INSTANCE = new Singleton();            }        }        return INSTANCE;}

Fix Double-checked Locking using Thread Local Storage

Only JDK5 or later provides volatile semantics, so we may still have problem while we are using JDK4 or lower version. Here we provide a version for JDK4 or lower which is came up by Alexander Terekhov. The performance of this version depends on JDK version. Compare to JDK2, JDK3 has significant improvement while JDK4 is even faster.

class Foo {    /** If perThreadInstance.get() returns a non-null value, this thread    has done synchronization needed to see initialization    of helper */    private final ThreadLocal perThreadInstance = new ThreadLocal();    private Helper helper = null;    public Helper getHelper() {        if (perThreadInstance.get() == null) createHelper();            return helper;    }    private final void createHelper() {        synchronized(this) {            if (helper == null)                helper = new Helper();        }        // Any non-null value would do as the argument here         perThreadInstance.set(perThreadInstance);    }}

Documentation of this method is provided here: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

0 0