一、是什么

确保一个类只有一个实例,并提供一个全局访问点

一般分类两大类: 饿汉模式、懒汉模式

使用: 以前在线白鹭H5游戏时,因为有很多的场景类, 而每个场景类不需要创建很多遍, 所以使用单例模式

二、示例

1. 饿汉模式

/**
 * 饿汉模式, 线程安全, 但默认就创建实例, 占用空间
 */
public class Singleton1 {
    private static final Singleton1 instance = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getIntance() {
        return instance;
    }
}

用空间换时间,默认就创建实例,所以没有线程安全问题

2. 懒汉模式

/**
 * 懒汉模式, 线程不安全
 */
public class Singleton2 {
    private static Singleton2 instance = null;

    private Singleton2() {

    }

    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }

        return instance;
    }
}

现在不安全在于,多个线程访问getInstance()时,当一个线程已经初始化了,而另外一个线程并没有感知,又重新创建了实例,这时候就不是单例

2.1 双检锁 Double-check

/**
 * 懒汉模式--双检索
 */
public class SingletonDoubleCheck {
    private static SingletonDoubleCheck instance = null;

    private SingletonDoubleCheck() {

    }

    private static SingletonDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (SingletonDoubleCheck.class) {
                if (instance == null) {
                    instance = new SingletonDoubleCheck();
                }
            }
        }

        return instance;
    }
}

为了在懒汉模式的基础上,保证线程安全, 出现了双检锁的设计,但是有出现了另一个问题。

在new SingletonDoubleCheck()时,是非原子性的,实际分为三步

  1. new 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间

但JVM编译器,为了性能考虑,可能重新排序2,3两个, 变为:

  1. new 分配内存空间
  2. 将对象指向刚分配的内存空间
  3. 初始化对象

举例说明:

  线程1检查到instance为空,获取锁,再次检查instance为空,为instance分配内存空间,指向内存空间,这时线程2检查到instance不为空,直接返回instance,但此时对象还没有初始化完成

2.2 双检锁 线程安全

/**
 * 使用volatile关键字的双检锁
 */
public class SingletonDoubleCheck2 {
    /**
     * volatile关键字保证我在锁instance时, 禁止JVM重排序
     */
    private volatile static SingletonDoubleCheck2 instance = null;

    private SingletonDoubleCheck2() {

    }

    private static SingletonDoubleCheck2 getInstance() {
        if (instance == null) {
            // 再次减少锁的范围, 只锁instance变量
            synchronized (instance) {
                if (instance == null) {
                    instance = new SingletonDoubleCheck2();
                }
            }
        }

        return instance;
    }
}

使用volatile关键字来禁止JVM重排序

3.3 内部类实现

/**
 * 静态内部类实现 -- 延迟加载
 *
 * 天生线程安全
 */
public class Singleton3 {

    /**
     * 私有化构造
     */
    private Singleton3() {
        System.out.println("初始化");
    }

    /**
     * 静态内部类
     */
    private static class InnerObject{
        private static Singleton3 instance = new Singleton3();
    }

    public static Singleton3 getInstance() {
        return InnerObject.instance;
    }

    public static void main(String[] args) {
        getInstance();
    }
}

3.4 静态代码实现

/**
 * 懒汉模式 -- 静态代码块实现
 */
public class Singleton4 {

    private static Singleton4 instance = null;

    static {
        instance = new Singleton4();
    }

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

外部类加载时并不需要立即加载内部类,所以可以起到延时加载的目录,

三、总结

单例模式是一个创建型的设计模式,能够帮助开发者创建一个唯一的实例

使用的还是挺频繁的

版权声明:本文为milicool原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/milicool/p/11278613.html