文章目录 1. 饿汉式 2. 懒汉式 3. DCL 双重校验锁懒汉式 4. 通过反射破坏DCL & 加锁阻止 5. 通过不调用 getInstance() 来破坏单例 6. 通过反射来干扰信号量,从而破坏单例 7. 通过枚举类实现单例,可以防止反射破坏单例
学 JUC 的时候顺便摸了下单例模式,看的是狂神的JUC教程~
1. 饿汉式
public class HungrySingleton { private byte [ ] data1 = new byte [ 1024 * 1024 ] ; private byte [ ] data2 = new byte [ 1024 * 1024 ] ; private byte [ ] data3 = new byte [ 1024 * 1024 ] ; private byte [ ] data4 = new byte [ 1024 * 1024 ] ; private static HungrySingleton INSTANCE = new HungrySingleton ( ) ; private HungrySingleton ( ) { } public static HungrySingleton getInstance ( ) { return INSTANCE; } public static void main ( String [ ] args) { System . out. println ( HungrySingleton . getInstance ( ) ) ; }
}
2. 懒汉式
public class LazySingleton { private static LazySingleton INSTANCE = null ; private LazySingleton ( ) { } public static LazySingleton getInstance ( ) { if ( INSTANCE == null ) { INSTANCE = new LazySingleton ( ) ; } return INSTANCE; }
}
3. DCL 双重校验锁懒汉式
Double Check Lock,判断了两次,加了一个 synchronized 锁住代码块 为什么要判断两次:可能多个进程卡在 synchronized 锁这步,所以进去后还要再判断一次 不安全的原因:指令重排(见代码注释) 解决方法:加 volatile 关键字禁止指令重排(见第三行注释代码)
public class DCLSingleton { private static DCLSingleton INSTANCE = null ; private DCLSingleton ( ) { } public static DCLSingleton getInstance ( ) { if ( INSTANCE == null ) { synchronized ( DCLSingleton . class ) { if ( INSTANCE == null ) { INSTANCE = new DCLSingleton ( ) ; } } } return INSTANCE; }
}
从字节码指令角度,分析指令重排影响:(图源黑马JVM视频,侵删) 可以看到,JIT 编译器可能会优化,先 putstatic,把引用地址赋予 INSTANCE,然后再 Method 来初始化 但是,并发情况下,可能在赋予引用后,init 前,有其他线程访问了 getInstance(),获得了一个还未引用的 INSTANCE,导致出错。
4. 通过反射破坏DCL & 加锁阻止
可以通过反射 setAccessible 无视私有,使用构造器来破坏单例(见 main() 代码) 阻止方法:在构造器再加一个 synchronized 锁,并且进行反射破坏判断并抛出异常
public class TripleSingleton { private static TripleSingleton INSTANCE = null ; private TripleSingleton ( ) { synchronized ( TripleSingleton . class ) { if ( INSTANCE != null ) { throw new RuntimeException ( "不要通过反射来破坏单例模式" ) ; } } } public static TripleSingleton getInstance ( ) { if ( INSTANCE == null ) { synchronized ( TripleSingleton . class ) { if ( INSTANCE == null ) { INSTANCE = new TripleSingleton ( ) ; } } } return INSTANCE; } public static void main ( String [ ] args) throws Exception { System . out. println ( TripleSingleton . getInstance ( ) ) ; Constructor < TripleSingleton > constructor = TripleSingleton . class . getDeclaredConstructor ( null ) ; constructor. setAccessible ( true ) ; System . out. println ( constructor. newInstance ( ) ) ; }
}
5. 通过不调用 getInstance() 来破坏单例
既然 4 是基于 INSTANCE != null 的情况来判断,那么我们只要不理 INSTANCE 就可以破坏单例模式了~ 只通过反射得到的构造器来创造多个实例 解决方法:加一个信号量 flag,在构造函数中防止这种破坏方式
public class TripleSingleton2 { private static TripleSingleton2 INSTANCE = null ; private static boolean flag = false ; private TripleSingleton2 ( ) { synchronized ( TripleSingleton2 . class ) { if ( flag == false ) { flag = true ; } else { throw new RuntimeException ( "不要通过反射来破坏单例模式" ) ; } } } public static TripleSingleton2 getInstance ( ) { if ( INSTANCE == null ) { synchronized ( TripleSingleton2 . class ) { if ( INSTANCE == null ) { INSTANCE = new TripleSingleton2 ( ) ; } } } return INSTANCE; } public static void main ( String [ ] args) throws Exception { Constructor < TripleSingleton2 > constructor = TripleSingleton2 . class . getDeclaredConstructor ( null ) ; constructor. setAccessible ( true ) ; System . out. println ( constructor. newInstance ( ) ) ; System . out. println ( constructor. newInstance ( ) ) ; }
}
6. 通过反射来干扰信号量,从而破坏单例
只要通过反射来干扰信号量,就可以继续破坏单例模式了~(见 main() 代码)
package singletons ; import java. lang. reflect. Constructor ;
import java. lang. reflect. Field ;
public class TripleSingleton3 { private static TripleSingleton3 INSTANCE = null ; private static boolean flag = false ; private TripleSingleton3 ( ) { synchronized ( TripleSingleton3 . class ) { if ( flag == false ) { flag = true ; } else { throw new RuntimeException ( "不要通过反射来破坏单例模式" ) ; } } } public static TripleSingleton3 getInstance ( ) { if ( INSTANCE == null ) { synchronized ( TripleSingleton3 . class ) { if ( INSTANCE == null ) { INSTANCE = new TripleSingleton3 ( ) ; } } } return INSTANCE; } public static void main ( String [ ] args) throws Exception { Field flag = TripleSingleton3 . class . getDeclaredField ( "flag" ) ; flag. setAccessible ( true ) ; Constructor < TripleSingleton3 > constructor = TripleSingleton3 . class . getDeclaredConstructor ( null ) ; constructor. setAccessible ( true ) ; TripleSingleton3 instance1 = constructor. newInstance ( ) ; flag. set ( instance1, false ) ; TripleSingleton3 instance2 = constructor. newInstance ( ) ; System . out. println ( instance1 + "\n" + instance2) ; }
}
7. 通过枚举类实现单例,可以防止反射破坏单例
为了进行反射破坏,先要获取 enum 类的构造器 观测源码,发现有无参构造函数,然而实际上这个是假的= =。 使用 javap 反编译,会发现还是有无参构造函数 使用 jad,会找到一个 private EnumSingle(String s, int i) 的构造函数,成功~
public enum EnumSingleton { INSTANCE; public static void main ( String [ ] args) throws Exception { System . out. println ( EnumSingleton . INSTANCE) ; Constructor < EnumSingleton > constructor = EnumSingleton . class . getDeclaredConstructor ( String . class , int . class ) ; System . out. println ( constructor. newInstance ( ) ) ; }
}
会爆出这个错误: 更加深入可以去看看 Enum 的源码~