欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

单例模式学习笔记

时间:2023-07-01
单例模式

饿汉式单例模式:

package com.zyc.playSingle;//饿汉式单例模式public class hungryMan { //私有构造器,这样就不能通过构造器创建实例了 private hungryMan(){ } private static final hungryMan HUNGR_MAN = new hungryMan(); //只要有就给:饿汉的特点,使用static这样可以直接用类访问 public static hungryMan getHungrMan(){ return HUNGR_MAN; }}class t1{ public static void main(String[] args) { hungryMan hungrMan = com.zyc.playSingle.hungryMan.getHungrMan(); hungryMan hungrMan1 = hungryMan.getHungrMan(); hungryMan hungrMan2 = hungryMan.getHungrMan(); System.out.println(hungrMan); System.out.println(hungrMan1); System.out.println(hungrMan2); }}

懒汉式单例模式:

package com.zyc.playSingle;//懒汉式单例模式public class lazyMan { private lazyMan(){ } private static lazyMan LAZY_MAN ; public static lazyMan getLAZY_MAN(){ if (LAZY_MAN==null){ LAZY_MAN= new lazyMan(); return LAZY_MAN; } return LAZY_MAN; }}class t2{ public static void main(String[] args) { lazyMan lazy_man = lazyMan.getLAZY_MAN(); lazyMan lazy_man1 = lazyMan.getLAZY_MAN(); System.out.println(lazy_man); System.out.println(lazy_man1); }}

单线程情况下我们使用单例模式是安全的,那么多线程呢?

package com.zyc.playSingle;public class DCLLazyMan { private DCLLazyMan(){ System.out.println(Thread.currentThread().getName()+"ing...."); //测试 每次打印创建单例的线程名 } private static DCLLazyMan dclLazyMan;//懒汉模式 public static DCLLazyMan getDclLazyMan(){ if(dclLazyMan==null){ dclLazyMan = new DCLLazyMan(); } return dclLazyMan; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ DCLLazyMan.getDclLazyMan(); }).start(); } }}

这里我们使用10个线程创建单例,结果如下:

我们看到结果不同,所以多线程情况下这个是线程不安全的

解决办法:加锁

//双重检测锁模式 DCL懒汉式 public static DCLLazyMan getDclLazyMan(){ if (dclLazyMan==null){ synchronized (DCLLazyMan.class){ if(dclLazyMan==null){ dclLazyMan = new DCLLazyMan(); } } } return dclLazyMan; }

使用双重检测锁我们看到结果如下:

结果现在满足了我们的需求,那么已经完全保证线程问题了吗?

指令重排

原因:创建对象dclLazyMan = new DCLLazyMan();这行代码不是一个原子性操作,可能会造成指令重排(1.分配内存空间2.初始化对象3.对象指向内存空间//这三步的顺序会重新排序,有可能先进行3操作在进行2)

所以我们需要使用volatile避免指令重排

更改后代码如下:

package com.zyc.playSingle;public class DCLLazyMan { private DCLLazyMan(){ System.out.println(Thread.currentThread().getName()+"ing...."); } private volatile static DCLLazyMan dclLazyMan; //双重检测锁模式 DCL懒汉式 public static DCLLazyMan getDclLazyMan(){ if (dclLazyMan==null){ synchronized (DCLLazyMan.class){ if(dclLazyMan==null){ dclLazyMan = new DCLLazyMan(); } } } return dclLazyMan; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ DCLLazyMan.getDclLazyMan(); }).start(); } }}

使用反射破解单例模式

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { DCLLazyMan dclLazyMan1 = DCLLazyMan.getDclLazyMan(); Constructor declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); DCLLazyMan dclLazyMan = declaredConstructor.newInstance(); System.out.println(dclLazyMan); System.out.println(dclLazyMan1); }

使用反射后可以发现创建两个对象不同;

解决办法,构造器进行判断

private static boolean flag = false; private DCLLazyMan(){ synchronized (DCLLazyMan.class){ if (flag==false){ flag=true; } else{ throw new RuntimeException("不要试图使用反射破坏异常"); } } System.out.println(Thread.currentThread().getName()+"ing...."); }

但是我们上面是用反射创建了一个对象和原本创建的对象进行比较,如果全部使用反射创建对象呢?

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); DCLLazyMan dclLazyMan = declaredConstructor.newInstance(); DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance(); System.out.println(dclLazyMan); System.out.println(dclLazyMan1); }

解决办法:使用标识符

private static boolean flag = false; private DCLLazyMan(){ synchronized (DCLLazyMan.class){ if (flag==false){ flag=true; } else{ throw new RuntimeException("不要试图使用反射破坏异常"); } } System.out.println(Thread.currentThread().getName()+"ing...."); }

但是如果通过反编译或者其他手段获得我们设置的标识符呢?

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { Field flag = DCLLazyMan.class.getDeclaredField("flag"); flag.setAccessible(true); Constructor declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); DCLLazyMan dclLazyMan = declaredConstructor.newInstance(); flag.set(dclLazyMan,false); DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance(); System.out.println(dclLazyMan); System.out.println(dclLazyMan1); }

通过修改标识符我们单例模式又被破坏了

解决办法:通过源码分析使用枚举

我们下面使用枚举类型进行分析

创建枚举类然后利用反射创建对象:

package com.zyc.playSingle;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException; //enum 是什么? enum本身就是一个Class 类 public enum enumSingle { INSTANCE; public enumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { enumSingle instance1 = enumSingle.INSTANCE; Constructor declaredConstructor = enumSingle.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); enumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }

报错如下:

我们发现结果跟源码中读的不同:

经过JAD工具进行反编译我们可以得到这里不是一个空参构造器,而是一个有参构造器

Constructor declaredConstructor = enumSingle.class.getDeclaredConstructor(String.class,int.class);

修改后:

抛出异常正确,成功使用枚举类避免了反射的破坏

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。