所谓单例模式就是确保某一个类只有一个实例(构造方法必须私有化),并且提供一个全局访问点。
单例模式有如下几个特点:
-
一、它只有一个实例。
-
二、它必须要自行实例化。
-
三、它必须自行想整个系统提供访问点。
单例模式分为饿汉式和懒汉式:
饿汉式都是线程安全的,通过类初始化来创建实例。
懒汉式 延迟创建这个实例对象,可以通过加锁和内部类初始化实现线程安全。
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; } }