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 的源码~