懒汉式的最基本写法
最后展示的代码包含详细的过程和注解,可以结合最后的代码来理解哦。
写法
我们先来看一下最基本的懒汉式写法,不考虑任何的安全问题
package singleton;
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName()+"ok");
}
private static LazyMan lazyMan = null;
public 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();
}
}
复制代码
结果
我们来看一下输出结果
通过图片,我们明显看出来这不遵循单例模式的要求,所以我们需要解决单例的线程安全问题。
双重检测模式来解决懒汉式线程安全问题
代码
下面是改进代码:
package singleton;
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName()+"ok");
}
private static LazyMan lazyMan = null;
public static LazyMan getInstance() {
if(lazyMan==null) {
synchronized (LazyMan.class) {
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();
}
}
复制代码
结果
写到这里,我们解决了线程的安全问题,这样看来,问题基本上解决了。但是我们面试的时候,这种写法是大部分人都能写出来的,所以我们需要一些骚操作。
懒汉式的一些骚操作
破坏双重检测锁模式
首先我们来破坏一下上面双重检测锁模式,下面是破坏代码演示
public static void main(String[] args) throws Exception {
LazyMan test1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan test2 = declaredConstructor.newInstance();
System.out.println(test1);
System.out.println(test2);
}
复制代码
我们来看一下结果
我们看到了此时的结果就不符合单例模式了。
解决破坏双重检测锁的操作
我们只需要一步就可以解决这个问题,就是手动抛出异常。实现操作如下:
private LazyMan() {
if(lazyMan!=null) {
throw new RuntimeException("不可以通过反射来破坏哦~");
}
}
复制代码
我们来看一下此时的结果:
我们可以看见此时通过一次反射问题就解决啦。 但是这里为什么写一次反射呢?不经有小伙伴问了,假如我俩次都是通过反射获得实例,是不是又要出事啦? 答案是肯定的。下面我们就继续深入这个问题。
继续破坏已经解决破坏双重检测锁的操作
直接上代码
public static void main(String[] args) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan test1 = declaredConstructor.newInstance();
LazyMan test2 = declaredConstructor.newInstance();
System.out.println(test1);
System.out.println(test2);
}
复制代码
我们来看一下结果
啊,真的被破坏了(^-^)V。但是魔高一尺,道高一丈,肯定有解决方法的。下面我们来看解决方法。
解决通过俩次反射得到实例的操作
话不多说,我们直接看代码
private static boolean del = false;
private LazyMan() {
synchronized (LazyMan.class){
if(del==false) {
del = true;
} else {
throw new RuntimeException("不可以通过反射来破坏哦~");
}
}
}
复制代码
我们来看一下结果
我们通过创建了del这个旗帜来判断,解决了这个问题。但是我们知道反射是无所不能的,所以这个解决方法还可以被破坏。
破坏旗帜
我们直接看代码
public static void main(String[] args) throws Exception {
Field del = LazyMan.class.getDeclaredField("del");
del.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan test1 = declaredConstructor.newInstance();
del.set(test1,false);
LazyMan test2 = declaredConstructor.newInstance();
System.out.println(test1);
System.out.println(test2);
}
复制代码
我们来看一下结果
我们可以看到单例模式又被破坏了。救命,到这里问题一直都没解决,这不就是套娃嘛,下面我们来说一下这种问题到底如何来解决。
最终解决方式
我们知道枚举是不能被反射拿到的,所以我们只能通过枚举类来防止反射的破坏。小伙伴们,你们想到了吗。 好了,这是我的对单例模式的懒汉式的介绍,欢迎大佬们指点。
最终代码如下(包含详细的注解)
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
//懒汉式单例
//道高一尺,魔高一丈,还是没能解决反射造成的安全问题,想解决得看枚举
public class LazyMan {
//这个的目的是解决俩次都是调用反射创建对象不安全的问题
private static boolean delTwoFanShe = false;
private LazyMan(){
// System.out.println(Thread.currentThread().getName()+"ok");
synchronized (LazyMan.class) {
if(delTwoFanShe == false) {
delTwoFanShe = true;
} else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
// if(lazyMan!=null) {
// //此方式是解决创建实例时一个用的getInstance(),另一个用的反射得到的,但是解决不了俩次都是通过反射获得的情况
// throw new RuntimeException("不要试图使用反射破坏异常");
// }
}
}
private volatile static LazyMan lazyMan = null;
//双重检测锁模式的懒汉式模式,简称DCL懒汉式
public static LazyMan getInstance() {
//上锁
if(lazyMan==null) {
synchronized (LazyMan.class) {
if(lazyMan==null) {
lazyMan = new LazyMan();//不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 假如不是按照123步骤执行的话,就是发生了指令重排,那么lazyMan就没有完成初始化操作
*/
}
}
}
return lazyMan; //如果不是按顺序执行123操作的话,此时lazyMan还没有完成构造,所以必须加上volatile来避免这种问题的发生
}
//我们写main函数来破坏,不断增强懒汉式的安全性
public static void main(String[] args) throws Exception {
// for(int i=0;i<10;i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
// LazyMan instance = LazyMan.getInstance(); //这是一般的获取方式
//我们得到自己设置的隐藏值,进行破坏
Field delTwoFanShe = LazyMan.class.getDeclaredField("delTwoFanShe");
delTwoFanShe.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan = declaredConstructor.newInstance();//通过反射得到实例对象
//我们修改第一次的实例,来将值变成false,进行破坏
delTwoFanShe.set(lazyMan,false);
LazyMan lazyMan1 = declaredConstructor.newInstance();//俩次实例对象都由反射来实现,造成破坏
System.out.println(lazyMan);
System.out.println(lazyMan1);
}
}
复制代码