- 基础概念
单例模式就是只需要创建一次,在整个应用生命周期都可以一直使用。
我们常分为饿汉式和懒汉式两种。
饿汉式
饿汉式是在初始化的时候就将单例对象创建出来。通常,通过属性new创建自身。该方式不存在线程安全的问题(JVM保证线程安全),但会造成内存资源的浪费。
我们可以创建一个这样的类:
1、定义私有化的成员变量:需初始化,用static修饰。
2、私有化构造器,防止其被其他类new。
3、对外提供公共方法,返回获取创建好的单例对象,用static修饰。
public class Singleton { // 私有化的成员变量:需初始化 private static Singleton singleton = new Singleton(); // 私有化构造器,防止其被其他类new private Singleton() { } // 对外提供公共方法,返回获取创建好的单例对象 public static Singleton getInstance() { return singleton; } public void otherMethod() { System.out.print("其他的行为方法"); } }
- 懒汉式
懒汉式是在第一次使用的时候,才将单例对象创建出来。该方式存在线程安全的问题,但不会造成内存资源的浪费。
我们可以创建一个这样的类:
1、定义私有化的成员变量:无需初始化,用static修饰。
2、私有化构造器,防止其被其他类new。
3、对外提供公共方法,返回获取创建好的单例对象。只有当不存在时候才new,存在则直接返回,用static修饰。
public class Singleton { // 私有化的成员变量:不做初始化 private static Singleton singleton = null; // 私有化构造器,防止其被其他类new private Singleton() { } // 对外提供公共方法,返回获取创建好的单例对象 public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } public void otherMethod() { System.out.print("其他的行为方法"); } }
懒汉式,怎么解决线程安全问题呢?
- 双重检查锁
两个线程同时访问的时候,我们可以加锁处理。
1、私有化的成员变量:不做初始化,volatile 保证原子性。
2、私有化构造器,防止其被其他类new。
3、对外提供公共方法,返回获取创建好的单例对象。加两层非空校验,将第二层校验为null的代码块用synchronized同步代码块。
public class Singleton { // 私有化的成员变量:不做初始化 private volatile static Singleton singleton = null; // 私有化构造器,防止其被其他类new private Singleton() { } // 对外提供公共方法,返回获取创建好的单例对象 public static Singleton getInstance() { // 第一层非空校验 if (singleton == null) { // 加同步锁,保证只有一个线程进入 synchronized (Singleton.class) { // 第二层非空校验,防止在第一次非空校验时,两个线程拿到的都是null对象而创建两次。 if (singleton == null) { singleton = new Singleton(); } } } return singleton; } public void otherMethod() { System.out.print("其他的行为方法"); } }
可以了解下对象在JVM中的创建步骤。以及线程相关知识点,同步,怎么保证原子性等。
- 静态内部类
1、私有化构造器,防止其被其他类new。
2、使用内部类(JVM保证),创建单例对象。
3、对外提供公共方法,通过调用内部类的属性,返回获取的创建好的单例对象。
public class StaticSingleton { // 私有化构造器,防止其被其他类new private StaticSingleton() { } // 使用内部类(JVM保证),创建单例对象 private static class SingletonFactory { private static StaticSingleton singleton = new StaticSingleton(); } // 对外提供公共方法,通过调用内部类的属性,返回获取的创建好的单例对象 public static StaticSingleton getInstance() { return SingletonFactory.singleton; } public void otherMethod() { System.out.print("其他的行为方法"); } }
- 枚举
public enum SingletonEnum { INSTANCE; public void otherMethod() { System.out.print("其他的行为方法"); } }
问题:使用上面的双重检查锁方式,我们如何破坏单例?
先上代码
/** * 单例攻击 */ public class SingletonAttack { public static void main(String[] args) throws Exception { // Singleton2 singleton1 = Singleton2.getInstance(); // Singleton2 singleton2 = Singleton2.getInstance(); // System.out.println(singleton1 == singleton2); // true // reflectAttack(); // reflectAttackWithThread(); // serializationAttack(); } /** 反射攻击测试 */ public static void reflectAttack() throws Exception { Singleton2 singleton1 = Singleton2.getInstance(); Constructor<Singleton2> constructor = Singleton2.class.getDeclaredConstructor(); // true-设置成员变量的暴力破解 constructor.setAccessible(true); // Singleton2 singleton1 = constructor.newInstance(null); Singleton2 singleton2 = constructor.newInstance(null); System.out.println(singleton1 == singleton2); } /** * 序列化深拷贝攻击测试 * 如果重写了readResolve()方法,则执行会报错ClassCastException */ public static void serializationAttack() throws Exception { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("objectFile")); Singleton2 singleton1 = Singleton2.getInstance(); os.writeObject(singleton1); os.close(); ObjectInputStream is = new ObjectInputStream(new FileInputStream(new File("objectFile"))); Singleton2 singleton2 = (Singleton2) is.readObject(); is.close(); System.out.println(singleton1 == singleton2); } /** 多线程下反射攻击测试 */ public static void reflectAttackWithThread() throws Exception { for (int i=0; i<100; i++) { Thread thread = new Thread(new Runnable() { public void run() { try { Constructor<Singleton2> constructor = Singleton2.class.getDeclaredConstructor(); // true-设置成员变量的暴力破解 constructor.setAccessible(true); Singleton2 singleton = constructor.newInstance(null); System.out.println(singleton); } catch (Exception e) { // 此处方便测试,不打印异常,防止因为异常而终止循环,因为要保证所有线程都能执行 // 如果输出多个不同实例,则表示破坏了单例 // 如果只输出一个实例,表示其他线程都无法获取实例对象,线程中途出现了异常 } } }); thread.start(); } } }
1.反射,通过反射获取单例对象的构造器,暴力破解后即可创建多个不同实例。怎么防止:私有构造方法加双重检查锁。
private volatile static boolean isFirstCreate = true; private Singleton() { // 这里双重校验,也是防止两个线程拿到的都是true,而创建了两个实例 if (isFirstCreate) { synchronized (Singleton.class) { if (isFirstCreate) { // 为第一次创建,将isFirstCreate设置为true isFirstCreate = false; } else { // isFirstCreate为true,表示之前创建过了,需要抛出异常 throw new RuntimeException("此单例对象已存在,禁止非法调用构造器!"); } } } else { // isFirstCreate为true,表示之前创建过了,需要抛出异常 throw new RuntimeException("此单例对象已存在,禁止非法调用构造器!"); } }
2.序列化,通过深克隆复制对象,可生成多个实例。怎么防止:重写在单例对象中readObject()方法。
如果单例对象实现了Serializable接口,我们可以通过在单例对象中重写readResolve(),禁止程序通过深拷贝创建多个实例,达到破坏单例对象的目的。
private Object readResolve() throws ObjectStreamException { return Singleton.class;
}
优化后的双重检查锁方式的单例类:
public class Singleton2 implements Serializable { // 私有化的成员变量:不做初始化 private volatile static Singleton2 singleton = null; private volatile static boolean isFirstCreate = true; // 私有化构造器,防止其被其他类new private Singleton2() { // 这里双重校验,也是防止两个线程拿到的都是true,而创建了两个实例 if (isFirstCreate) { synchronized (Singleton2.class) { if (isFirstCreate) { // 为第一次创建,将isFirstCreate设置为true isFirstCreate = false; } else { // isFirstCreate为true,表示之前创建过了,需要抛出异常 throw new RuntimeException("此单例对象已存在,禁止非法调用构造器!"); } } } else { // isFirstCreate为true,表示之前创建过了,需要抛出异常 throw new RuntimeException("此单例对象已存在,禁止非法调用构造器!"); } } // 对外提供公共方法,返回获取创建好的单例对象 public static Singleton2 getInstance() { // 第一层非空校验 if (singleton == null) { // 加同步锁,保证只有一个线程进入 synchronized (Singleton2.class) { // 第二层非空校验,防止在第一次非空校验时,两个线程拿到的都是null对象而创建两次。 if (singleton == null) { singleton = new Singleton2(); } } } return singleton; } public void otherMethod() { System.out.print("其他的行为方法"); } /** 单例对象实现了Serializable接口,通过重写readResolve()禁止程序通过深拷贝创建多个实例,达到破坏单例对象的目的 */ private Object readResolve() throws ObjectStreamException { return Singleton2.class; } }