单例模式世界上只有两种人,懒人和漫无目的的人,作为一名工程师如果不勤奋且没有目标,淘汰是必然的
学无止境 点我 跳转到Java工程师必备技术栈
一、简介二、类型三、饿汉模式
3.1、正常创建3.2、静态内部类 四、懒汉模式
4.1、单线程使用4.2、双重检测锁(DCL)+原子性操作 五、反射与单例的斗争
5.1、反射破坏单例5.2、私有构造加锁--三重检测锁5.3、反射创建对象5.4、私有构造加标记判断5.5、若标记被找到 六、枚举(无法被反射破坏)
6.1、使用6.2、验证
一、简介单例模式是指在内存中只会创建且仅创建一次对象的设计模式。
在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升
单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
饿汉式:在类加载时已经创建好对象,等待被程序使用懒汉式:在需要使用对象时才去创建该单例对象 三、饿汉模式 3.1、正常创建
构造器私有(外部无法直接new对象)手动创建好一个类对象提供方法给外部获取该对象
public class Hungry {
//构造器私有,外部无法直接new对象
private Hungry() {}
//手动创建静态单实例
private final static Hungry HUNGRY = new Hungry();
//给外部提供获取该单实例的方法
public static Hungry getInstance() {
return HUNGRY;
}
}
缺点:可能会浪费内存空间
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];3.2、静态内部类
public class Holder {
private Holder() {
}
private static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
四、懒汉模式 4.1、单线程使用
存在线程不安全的问题
public class Lazy {
private Lazy() {}
private static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
4.2、双重检测锁(DCL)+原子性操作
new对象不是原子性操作,内部执行以下
- 分配内存空间执行构造方法,初始化对象把对象指向内存空间
但jvm有可能出现指令重排(执行132)
A线程执行了13,这个时候lazy的内存空间不等于null,但其实对象还未初始化B线程进来了,虽然B进不去同步代码块,但同步代码块前的空判断会判断为非空此时lazy未完成构造,会直接return空对象
可以使用volatile关键字防止指令重排的发生
public class Lazy {
private Lazy() {}
//防止初始化过程指令重排
private volatile static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}
五、反射与单例的斗争
5.1、反射破坏单例
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Lazy instance1 = Lazy.getInstance();
Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//无视私有构造
declaredConstructor.setAccessible(true);
Lazy instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
5.2、私有构造加锁–三重检测锁
但这种只能存在先获取了单例,再通过反射创建对象,如果对象都是反射创建的,这样也无法避免
private Lazy() {
synchronized (Lazy.class) {
if (lazy != null) {
throw new RuntimeException("请勿使用反射破坏单例");
}
}
}
5.3、反射创建对象
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//Lazy instance1 = Lazy.getInstance();
Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//无视私有构造
declaredConstructor.setAccessible(true);
Lazy instance2 = declaredConstructor.newInstance();
Lazy instance3 = declaredConstructor.newInstance();
System.out.println(instance2);
System.out.println(instance3);
}
5.4、私有构造加标记判断
private static boolean laptoy = false;
private Lazy() {
synchronized (Lazy.class) {
if (laptoy == false) {
laptoy = true;
} else {
throw new RuntimeException("请勿使用反射破坏单例");
}
}
}
5.5、若标记被找到
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//Lazy instance1 = Lazy.getInstance();
Field laptoy = Lazy.class.getDeclaredField("laptoy");
laptoy.setAccessible(true);
Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//无视私有构造
declaredConstructor.setAccessible(true);
Lazy instance2 = declaredConstructor.newInstance();
laptoy.set(instance2, false);
Lazy instance3 = declaredConstructor.newInstance();
System.out.println(instance2);
System.out.println(instance3);
}
六、枚举(无法被反射破坏)
6.1、使用
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test {
public static void main(String[] args) {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE; //相同
}
}
6.2、验证
1、从生成的target目录发现EnumSingle源码类有私有构造
2、尝试用反射newInstance
class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
3、报错,没有此类型的构造器,也就是没有私有无参构造器
4、达不到预期,应该是抛出无法反射地创建枚举对象
5、反编译该类,的确有该构造,但是反射是绝对正确的
6、使用更专业的反编译工具,发现该类没有无参构造,但有一个有参构造
class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
达到预期效果,反射无法破坏单例



