首页 > 技术文章 > 单例模式

yuancoco 2019-11-21 15:37 原文

所谓单例模式就是确保某一个类只有一个实例(构造方法必须私有化),并且提供一个全局访问点。

单例模式有如下几个特点:

  • 一、它只有一个实例。

  • 二、它必须要自行实例化。

  • 三、它必须自行想整个系统提供访问点。

单例模式分为饿汉式和懒汉式:

饿汉式都是线程安全的,通过类初始化来创建实例。

懒汉式 延迟创建这个实例对象,可以通过加锁和内部类初始化实现线程安全。

 JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。

 

/**
 * 饿汉式 在类初始化时直接创建对象,不存在线程安全问题,不管你是否需要这个对象都会创建
 * <p>
 * 1. 构造器私有化
 * 2. 自行创建,并且用静态变量保存
 * 3. 向外提供这个实例
 * 4. 强调这是一个单例,我们可以用final修饰,不能改变引用,可以改变属性。
 *
 * @author yuan
 */
public class Singleton1 {
    public static final Singleton1 INSTANCE = new Singleton1();

    private static Date date = new Date();

    private Singleton1() {

    }
}



/**
 * 饿汉式 线程安全
 * <p>
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定成一个,就成了单例
 * <p>
 * public enum Size{ SMALL, MEDIUM, LARGE, EXTRA_LARGE };
 * 实际上,这个声明定义的类型是一个类,它刚好有四个实例,在此尽量不要构造新对象。
 * https://www.cnblogs.com/liaojie970/p/6474733.html
 *
 * @author yuan
 */
public enum Singleton2 {
    /**
     * 相当于 Singleton2 INSTANCE = new Singleton2()
     * 反编译可以看到这个类的构造函数是私有的: private Singleton2() {}
     */
    INSTANCE
}



/**
 * 饿汉式 线程安全
 * <p>
 * 静态代码块饿汉式,也是线程安全的
 * 适合于需要灵活初始化属性
 *
 * @author yuan
 */
@Data
public class Singleton3 {
    public static final Singleton3 INSTANCE;

    private String info;

    static {
        Properties pro = new Properties();
        try {
            //通过类加载器加载配置文件,需要特别注意文件路径否则NullPointerException
            pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        INSTANCE = new Singleton3(pro.getProperty("info"));
    }

    private Singleton3(String info) {
        this.info = info;
    }
}



/**
 * 懒汉式 线程不安全
 * 延迟创建这个实例对象
 * 1. 构造器私有化
 * 2. 用一个静态变量保存这个唯一的实例
 * 3. 提供一个静态方法,获取这个实例对象
 *
 * @author yuan
 */
public class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {

    }

    /**
     * 线程不安全
     *
     * @return
     */
    public static Singleton4 getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Singleton4();
        }
        return instance;
    }
}



/**
 * 懒汉式 线程安全
 * 延迟创建这个实例对象
 * 1. 构造器私有化
 * 2. 用一个静态变量保存这个唯一的实例
 * 3. 提供一个静态方法,获取这个实例对象
 *
 * @author yuan
 */
public class Singleton5 {
/**
 * instance = new Singleton5();
 *
 * 不是原子性操作,至少会经过三个步骤:
 *
 * 1. 分配内存
 * 2. 执行构造方法
 * 3. 指向地址
 *
 * 由于指令重排,导致A线程执行 instance = new Singleton5();的时候,可能先执行了第三步(还没执行第二步),
 * 此时线程B又进来了,发现instance已经不为空了,直接返回了instance,并且后面使用了返回的instance,
 * 由于线程A还没有执行第二步,导致此时instance还不完整,可能会有一些意想不到的错误,所以就有了下面一种单例模式。
 *
 * 这种单例模式增加一个volatile关键字来避免指令重排:
 */
private static volatile Singleton5 instance;
private Singleton5() {
}

//线程安全
public static Singleton5 getInstance() { //只有为null时才进行加锁,不为null直接返回实例,优化代码效率 if (instance == null) { synchronized (Singleton5.class) { if (instance == null) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton5(); } } } return instance; } } /** * 懒汉式 线程安全 * 在内部类被加载和初始化时,才创建INSTANCE实例对象 * 静态内部类不会自动随着外部内的加载和初始化而初始化,它是需要单独去加载和初始化的 * 因为是在内部类加载和初始化时,创建的,因此是线程安全的 * <p> * JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作, * 其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。 * * @author yuan */ public class Singleton6 { private Singleton6() { } private static class Inner { private static final Singleton6 INSTANCE = new Singleton6(); } public static Singleton6 getInstance() { return Inner.INSTANCE; } }

 

推荐阅读