单例模式的 8 种实现
饿汉式单例模式
静态常量(线程安全)
public class Singleton1 {private final static Singleton1 INSTANCE = new Singleton1();private Singleton1() {}public static Singleton1 getInstance() {return INSTANCE;}}
静态代码块(线程安全)
public class Singleton2 {private final static Singleton2 INSTANCE;static {INSTANCE = new Singleton2();}private Singleton2() {}public static Singleton2 getInstance() {return INSTANCE;}}
懒汉式单例模式
线程不安全
public class Singleton3 {private static Singleton3 instance;private Singleton3() {}public static Singleton3 getInstance() {if (instance == null) {instance = new Singleton3();}return instance;}}
安全同步方法(线程安全,但效率低)
public class Singleton4 {private static Singleton4 instance;private Singleton4() {}public synchronized static Singleton4 getInstance() {if (instance == null) {instance = new Singleton4();}return instance;}}
部分同步方法(线程不安全)
public class Singleton5 {private static Singleton5 instance;private Singleton5() {}public static Singleton5 getInstance() {if (instance == null) {synchronized (Singleton5.class) {instance = new Singleton5();}}return instance;}}
部分同步方法(线程安全,推荐使用)
public class Singleton6 {private volatile static Singleton6 instance;private Singleton6() {}public static Singleton6 getInstance() {if (instance == null) { // Asynchronized (Singleton6.class) {if (instance == null) { // Binstance = new Singleton6();}}}return instance;}}
第一个 if 是并行的,可以提高效率;第二个 if 是为了防止多个线程等待进入锁而发生多次创建的。这个类的功能是,当实例已经被创建过,则不会经过加锁,直接返回当前实例;否则就加锁,创建实例。这样可以大幅度提高性能,不用每次都加锁,只要对象已经创建成功,getInstance()方法将不需要获取锁,直接返回已创建好的对象。
至于为什么要用 volatile修饰,这是因为在执行到 A 或 B 处,如果另一个线程 2 读取到instance不为 null 时,那么线程 2 就认为对象已经创建过了,直接返回,然后拿来使用,但是此时instance引用的对象可能还没有初始化完成,那么这样一来,如果线程 2 获取这个对象的属性时就会发生空指针异常。
实际上,对于instance = new Singleton6();这行代码,可以分解为如下 3 行伪代码:
memory = allocate(); // 1.分配对象的内存空间ctorInstance(memory); // 2.初始化对象instance = memory; // 3.设置instance指向刚分配的内存地址
由于 2 和 3 之间没有数据依赖性,因此可以进行重排序,重排序后如下:
memory = allocate(); // 1.分配对象的内存空间instance = memory; // 3.设置instance指向刚分配的内存地址(此时对象还未初始化)ctorInstance(memory); // 2.初始化对象
虽然 2 和 3 交换了顺序,但是单线程内执行的结果没有改变,然而多线程下执行的结果就不同了,如下图所示。
当线程 A 和 B 按上图顺序执行时,B 线程看到的是一个还没有被初始化的对象。
为了解决上述问题,我们需要在初始化的时候,给它加上一个 volatile 关键字禁止重排序。
volatile禁止重排序的规则:•
volatile写之前禁止重排序•volatile读之后禁止重排序
静态内部类(线程安全,推荐使用)
public class Singleton7 {private Singleton7() {}private static class SingletonInstance {private static final Singleton7 INSTANCE = new Singleton7();}public static Singleton7 getInstance() {return SingletonInstance.INSTANCE;}}
由于使用了静态内部类,相比饿汉式的直接实例化,静态内部类方式可以在需要初始化的时候才初始化,即按需加载。
枚举类型单例模式(线程安全,最佳方法)
public enum Singleton8 {INSTANCE;public void getInstance() {// return whatever}}
具体示例:
class User {// 私有化构造函数private User() {}// 定义一个静态枚举类static enum SingletonEnum {// 创建一个枚举对象,该对象天生为单例INSTANCE;private User user;// 私有化枚举的构造函数,只会调用一次private SingletonEnum() {user = new User();}public User getInstance() {return user;}}// 对外暴露一个获取User对象的静态方法public static User getInstance() {return SingletonEnum.INSTANCE.getInstance();}}public class Test {public static void main(String[] args) {// trueSystem.out.println(User.getInstance() == User.getInstance());}}
彩蛋
反射攻击破坏单例
public class Singleton {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Singleton first = Singleton.getInstance();Singleton second = Singleton.getInstance();Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton third = constructor.newInstance();System.out.println(first + "\n" + second + "\n" + third);System.out.println("正常情况下,实例化两个实例是否相同:" + (first == second));System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:" + (first == third));}
// 结果Singleton@74a14482Singleton@74a14482Singleton@1540e19d正常情况下,实例化两个实例是否相同:true通过反射攻击单例模式情况下,实例化两个实例是否相同:false
可以看到,反射获取的并不是同一个实例。
序列化破坏单例
// 让单例类实现序列化接口public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
public static void main(String[] args) throws IOException, ClassNotFoundException {File file = new File("singleton_file");// 第一次获取实例Singleton instance = Singleton.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));oos.writeObject(instance);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));// 第二次获取实例Singleton newInstance = (Singleton) ois.readObject();System.out.println(instance);System.out.println(newInstance);System.out.println("序列化前后两个实例是否相同:" + (instance == newInstance));}
// 结果Singleton@7f31245aSingleton@568db2f2序列化前后两个实例是否相同:false
我们先将第一次获取的实例写入文件,然后第二次读取这个实例,再次获取这个实例,结果表明这两个实例是不同的实例,即通过序列化和反序列化拿了不同的对象。
但我们需要拿到相同对象,那么需要在单例类中加入一个 readResolve() 方法,由于任何一个readObject() 方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例,因此我们只需要让 readObject() 方法返回初始化时的实例对象即可。
修改如下:
public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}// 加上这个方法private Object readResolve() {return singleton;}}
// 结果Singleton@7f31245aSingleton@7f31245a序列化前后两个实例是否相同:true
枚举类型避免反射攻击
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance() {return INSTANCE;}public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {EnumSingleton singleton1 = EnumSingleton.INSTANCE.getInstance();EnumSingleton singleton2 = EnumSingleton.INSTANCE.getInstance();System.out.println("正常情况下,实例化两个实例是否相同:" + (singleton1 == singleton2));Constructor<EnumSingleton> constructor = null;constructor = EnumSingleton.class.getDeclaredConstructor();constructor.setAccessible(true);EnumSingleton singleton3 = null;singleton3 = constructor.newInstance();System.out.println(singleton1 + "\n" + singleton2 + "\n" + singleton3);System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:" + (singleton1 == singleton3));}}
// 结果正常情况下,实例化两个实例是否相同:trueException in thread "main" java.lang.NoSuchMethodException: concurrency.singleton.EnumSingleton.<init>()at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at concurrency.singleton.EnumSingleton.main(EnumSingleton.java:22)
可以看到,反射攻击抛出异常了。
枚举类型避免序列化问题
public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}public static void main(String[] args) throws IOException, ClassNotFoundException {File file = new File("singleton_file");//第一次获取实例EnumSingleton instance = EnumSingleton.INSTANCE.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));oos.writeObject(instance);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));//第二次获取实例EnumSingleton newInstance = (EnumSingleton) ois.readObject();System.out.println(instance);System.out.println(newInstance);System.out.println("序列化前后两个实例是否相同:" + (instance == newInstance));}}
// 输出INSTANCEINSTANCE序列化前后两个实例是否相同:true
