饿汉式单例模式:
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
使用反射后可以发现创建两个对象不同;
解决办法,构造器进行判断
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
解决办法:使用标识符
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
通过修改标识符我们单例模式又被破坏了
解决办法:通过源码分析使用枚举
我们下面使用枚举类型进行分析
创建枚举类然后利用反射创建对象:
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
报错如下:
我们发现结果跟源码中读的不同:
经过JAD工具进行反编译我们可以得到这里不是一个空参构造器,而是一个有参构造器
Constructor
修改后:
抛出异常正确,成功使用枚举类避免了反射的破坏