6种单例模式写法了解一下

6种单例模式写法了解一下

饿汉式 (线程都是安全滴)

简单饿汉式(直接实例化)

public class HungerSimpleSingleton {
    public static final HungerSimpleSingleton instance = new HungerSimpleSingleton();
    private HungerSimpleSingleton(){}
     
}

静态代码块(因为在类加载的时候只执行一次)

public class HungerStaticSingleton {
    public static final SingletonObj INSTANCE;
    static {
    //假如这里还需要加载外部资源等复杂逻辑
        INSTANCE =  new SingletonObj();
    }
    private HungerStaticSingleton(){}
}

枚举单例(反编译可以看出端倪)

public enum EnumSingleton {
    INSTANCE;
}

反编译后的结果,见文章
Java的Enum枚举反编译的结果

懒汉式 (线程有安全也有不安全)

简单懒汉式(线程不安全)

public class LazySimpleSingleton {
    private static LazySimpleSingleton instance;
    private LazySimpleSingleton(){}
    private static LazySimpleSingleton getInstance(){
        //这里也是产生了竞态条件(Race Condition),导致了线程不安全)
        if (instance == null){
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

同步懒汉式(使用了volatile以及synchronized,双重判断)

第一种 同步方法

public class LazySyncSingleton {
    private static LazySyncSingleton instance;
    private LazySyncSingleton(){}
    //这里使用了 synchronized
    public synchronized static LazySyncSingleton getInstance(){
                if (instance == null) {
                    try {
                        //为了保证效果,这里睡2秒
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new LazySyncSingleton();
                }
        return instance;
    }
}

第二种 同步代码块+volatile+double-check

这一种有些麻烦,要考虑的问题很多,一是效率问题,再同步代码块外部增加了if判断,二是指令重排问题,虽然我们理想的new对象应该是①申请内存空间②实例化对象③内存地址指向变量,但是由于②③没有数据依赖在编译过程中,虚拟机可能会帮我们“优化”顺序,变为③②,即造成了先指向了变量,但是实例化未完成。当然,单线程环境会保证最终一致性。

public class LazyVolatileSingleton {
    //防止指令重排
    private static volatile LazyVolatileSingleton instance;
    private LazyVolatileSingleton(){}
    public static LazyVolatileSingleton getInstance(){
        //这里要注意,如果没有数据依赖的话,多线程环境可能会出现指令重排,执行的顺序不会按照代码的顺序执行
        //如果这里是有两条线程,a线程正在初始化,这个时候b现在开始判断,instance!=null,但是地址指向的是null。
        //因为在new对象的过程中是先申请内存空间,再实例化对象,如果这里刚申请完空间给instance,那么它不为空,但是其他线程根据
        //这个地址去找的时候,优化a线程new操作还未完成,所以这里有地址指向,但是还是“空”的,所以这也是造成了线程安全问题。

        //加快效率,如果顺利的判断不等于null了可以不用获取锁直接返回
        if (instance == null) { //TODO 这一行要注意
            //使用内置锁,保证不被其他线程干扰,保证操作原子性
            synchronized (LazyVolatileSingleton.class) {
                if (instance == null) {
                    try {
                        //为了保证效果,这里睡2秒
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new LazyVolatileSingleton();//TODO 这一行要注意
                }
            }
        }
        return instance;
    }
}

静态内部类懒汉式

/**
 * 在内部类被加载初始化的时候,才会创建父类的INSTANCE对象
 */
public class LazyInnerSingleton {
    private LazyInnerSingleton(){}
    private static class  Inner{
        private static LazyInnerSingleton INSTANCE = new LazyInnerSingleton();
    }
    public static LazyInnerSingleton getInstance(){
        return Inner.INSTANCE;
    }
}
发布了51 篇原创文章 · 获赞 29 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/u012190514/article/details/105564463