Design patterns - several implementations of singletons

Hungry Han Singleton : Initialize directly when the class is loaded

Advantages: simple, safe

Disadvantages: Sometimes the class is initialized when it is not required to load, hoping to delay loading.

code

public class Singleton{

    // Constructor private

    private Singleton(){};

    

    // static members

    private static final Singleton singleton = new Singleton();

    public static Singleton getInstance(){

        return singleton;

    }

}

Lazy Singleton : Only create singletons when they are actually used

1. Single-threaded simple singleton

code

public class Singleton{

    // Constructor private

    private Singleton(){};

    

    // static members

    private static Singleton singleton;

    public static Singleton getInstance(){

        // Created when used for the first time

        if(singleton == null){ 

            singleton = new Singleton();

        }

        // Then return directly

        return singleton;

    }

}

Problems with multi-threading: multiple threads enter the if condition at the same time and create multiple instances, which violates the original intention of the singleton.

2. Use built-in lock protection: Use synchronized built-in locks to ensure that only one thread can enter the critical section

code

public class Singleton{

    // Constructor private

    private Singleton(){};

    

    // static members

    private static Singleton singleton;

    // Use synchronized to lock the critical section

    public static synchronized Singleton getInstance(){

        // Created when used for the first time

        if(singleton == null){ 

            singleton = new Singleton();

        }

        // Then return directly

        return singleton;

    }

}

The disadvantage is that every time a singleton is obtained, a lock must be obtained. Under high concurrency, the built-in lock will be upgraded to a heavyweight lock, which is expensive and has poor performance.

3. 双重检查锁:只在第一次创建的时候进行加锁,所以,先判断单例是否已被初始化,如果没有,加锁后再初始化。

代码

public class Singleton{

    //构造器私有

    private Singleton(){};

    

    //静态成员

    private static Singleton singleton;

    public static Singleton getInstance(){

        if(singleton == null){ //一重检查

            synchronized(Singleton.class){ //加锁

                if(singleton == null){ //二重检查

                    singleton = new Singleton();

                }

            }

        }

        //随后直接返回

        return singleton;

    }

}

在进行第一重检查的时候不需要加锁,多个线程可以同时进入,但是只有一个线程能获取到锁并创建单例,其余线程随后可能获取到锁,但是无法通过第二重检查,所以直接返回单例。相比锁住整个getInstance方法,锁的粒度更小了,性能更好。

4. 双重检查锁+volatile: 表面上看双重检查锁天衣无缝了,但是需要注意:初始化单例的语句:

singleton = new Singleton();

并不是一个原子操作,经过汇编之后大致会被分成三个步骤:

1)分配一块内存M.

2) 在内存M上初始化Singleton对象

3)把M的地址赋给singleton变量

编译器、CPU都可能会对没有内存屏障和数据依赖关系的操作进行重排序,上述三个指令可能被优化成1)3)2)。这就可能导致线程A先进入临界区并且分配了内存,把地址赋给了singleton变量,但是还没有初始化对象,这时候线程B调用getInstance方法,发现singleton != null,直接返回一个未初始化的对象。

解决方法是用volatile关键字禁止指令重排。

代码

public class Singleton{

    //构造器私有

    private Singleton(){};

    

    //静态成员,使用volatile保持可见性,禁止指令重排

    private static volatile Singleton singleton;

    public static Singleton getInstance(){

        if(singleton == null){ //一重检查

            synchronized(Singleton.class){ //加锁

                if(singleton == null){ //二重检查

                    singleton = new Singleton();

                }

            }

        }

        //随后直接返回

        return singleton;

    }

}

 

实际上,只有很低版本的 Java 才会有这个问题。我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。

静态内部类实现单例:虽然双重检查锁能实现高性能、线程安全的单例模式,但是写法繁琐,利用静态内部类可以实现简单且安全的单例。

代码

public class Singleton{

    //构造器私有

    private Singleton(){};

    

    //静态内部类

    private static class LazyHolder{

        //通过final保障初始化的线程安全

        private static final Singleton singleton = new Singleton();

    }

    

    public static final Singleton getInstance(){

        return LazyHolder.singleton;

    }

}

在外部类被加载的时候并不会创建内部类的实例对象,只有getInstance()被调用的时候才会去加载内部类并且初始化单例。

但是静态内部类有一个致命的缺点:外部无法传入参数

Guess you like

Origin blog.csdn.net/weixin_42371376/article/details/132116233