单例模式笔记
作者:互联网
目录
饿汉式
一开始就将对象初始化,不管用没用该类都去做分配空间,初始化对象,将对象指向空间
缺点:浪费空间
public class HungerMan {
private HungerMan() {
}
private final static HungerMan hungerMan = new HungerMan();
private static HungerMan getInstance() {
return hungerMan;
}
}
懒汉式
用到才去初始化对象,节约空间
缺点:只是用于单线程,程序要做并发则需要使用双检锁
public class LazyMan {
private LazyMan() {
}
private static LazyMan lazyMan ;
private static LazyMan getInstance() {
//为空时才去初始化对象
if(lazyMan==null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for(int i=0;i<10;i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
双检锁 : 给上面代码增加一层锁,使用线程同步 synchronized
分析:
- lazyMan = new LazyMan();不是原子性操作
实际上是三个步骤:
* 分配内存空间
* 初始化构造方法实例对象
* 将对象指向该空间 - 出现指令重排
* 启动线程可能执行123,也可能执行132
* 如果线程A执行132,那么轮到线程B先判断对象不为null,由于对象执行顺序不为123,就会出现返回对象没有初始化
解决方法:加volatile
public class LazyMan {
//私有构造器
private LazyMan() {
System.out.println(Thread.currentThread().getName()+"ok");
}
//加上volatile 原子性操作
private volatile static LazyMan lazyMan ;
private static LazyMan getInstance() {
/**
* 使用双检锁
*/
if(lazyMan==null) {
synchronized (LazyMan.class) {
if(lazyMan==null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
//1.
for(int i=0;i<10;i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
静态内部类为独立层,效果与双检锁一样
package workspace.单例模式;
//静态内部类
public class Inside {
private Inside() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private static Inside getInstance() {
return Man.INSIDE;
}
private static class Man {
private final static Inside INSIDE = new Inside();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Inside.getInstance();
}).start();
}
}
}
枚举与反射
除了枚举所有的方法都是不安全的,都可以使用反射去破坏,不过反射是人为去破坏的,使用双检索或者静态内部类已经足够。
反射机制
通过反射可以直接构造空参函数
public static void main(String[] args) throws Exception{
//正常实例化
LazyMan lazyMan2 = LazyMan.getInstance();
System.out.println(lazyMan2);
//反射破坏
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan lazyMan = constructor.newInstance();
System.out.println(lazyMan);
}
两个对象结果不一样反射成功破坏
解决方法:给空参函数加锁
private LazyMan() {
synchronized (LazyMan.class) {
if(lazyMan!=null) {
throw new RuntimeException("不要视图通过反射破坏");
}
}
}
破解:
初始化对象两个都为反射获取空参对象
对抗
可加入标志密钥防止放反射
但是这样依然是不安全的,因为可以通过jad反编译代码知道jingQing,将其修改回false反射仍然可以破坏。
枚举不可反射破坏
通过jad反编译枚举可以发现枚举中没有空参构造函数,因此枚举不能被破坏。
编写测试代码
返回结果不为
则不是我们想要的反射破坏失败结果
去反编译
-
javap -p 反编译结果,显示我们的构造函数为有参。
-
jad反编译源码中的是有参构造函数。
再去破坏
反射不能破坏枚举单例模式
标签:反射,笔记,private,lazyMan,static,模式,单例,new,LazyMan 来源: https://blog.csdn.net/liaojsgtcg/article/details/121683141