学自B站,遇见狂神
缺点:可能会浪费空间,开辟了空间,却没有使用 Hungry.class
package com.wu; public class Hungry { //可能会浪费空间,开辟了空间,却没有使用 byte[] date1= new byte[1024*1024]; byte[] date2= new byte[1024*1024]; byte[] date3= new byte[1024*1024]; //第一步,私有化构造器 private Hungry(){ } //得到该类的一个实例 private static final Hungry hungry = new Hungry(); //提供得到该类实例的接口 public static Hungry getInstance(){ return hungry; } }缺点:在多线程并发模式下容易出现并发问题。 在懒汉式基础上改进的DCL(双重检测+锁+原子操作)懒汉式解决并发问题。
问题截图(每次运行的出现的线程数还不一样)
注意:synchronized 解决并发问题,lzayMan=new LzayMan();//不是原子操作(跳出if执行return lzayMan,出现了分割操作),可能发生指令重排序的问题,通过volatil来解决。
volatile 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语 义 (1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。 (2) 禁止进行指令重排序 volatile 不是原子性操作,volatitle保证部分有序性,当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行; x = 2; // 语 句 1 y = 0; // 语 句 2 flag = true; //语句3 x = 4; // 语 句 4 y = -1; // 语 句 5假设flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是 不作任何保证的。
原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作 一个很经典的例子就是银行账户转账问题:比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。 package com.wu.DCL; public class LzayMan { private LzayMan(){ System.out.println(Thread.currentThread().getName()+"OK"); } private volatile static LzayMan lzayMan ;//volatile解决指令重排序 //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LzayMan getInstance(){ if(lzayMan == null){ synchronized (LzayMan.class )//锁定一个类,线程串行执行 { if(lzayMan == null){ lzayMan=new LzayMan();//不是一个原子性操作 } } /* * 1.分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向者个空间 * * 123 * 132 A线程执行顺序 * * B线程 //此时lazyMan还没有完成构造,因此lazyMan=null,跳出if条件 * * */ } return lzayMan; } //测试 public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LzayMan.getInstance(); }).start(); } } }内部类单例,虚拟机加载内部类调用getInstance()产生的实例,能够保证线程安全,但是反射技术可以通过获取无参破坏单例:
package com.wu; import java.lang.reflect.Constructor; public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.holder; } private static class InnerClass{ private static Holder holder = new Holder(); } public static void main(String[] args) throws Exception { //反射产生对象 Constructor<Holder> declaredConstructor = Holder.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true);//允许访问私有私有属性 Holder holder1 = Holder.getInstance(); Holder holder2 = declaredConstructor.newInstance(); System.out.println(holder1); System.out.println(holder2); } }使用反射来测试枚举
class Test{ public static void main(String[] args) throws Exception { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }但这并不是我们想看到的结果!!!
com.wu.EnumSingle.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at com.wu.Test.main(EnumSingle.java:16)枚举: 1、普通的反编译会欺骗开发者,说enum枚举是无参构造 2、实际enum为有参构造(见后面); 3、通过反射破解枚举会发现抛出异常(想要的结果) :Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.wu.Test.main(EnumSingle.java:17)
通过jad工具反编译EnumSingle.class查看具体原因
javap -p EnumSigle.class结果如下: 但事实上这个JDK和IDEA自带的jad也欺骗了我们,通过自备的开发工具jad放置与目标类文件一个目录下,双击jad.exe输入命令行(见下面)反编译枚举
jad -sjava EnumSingle.class反编译枚举EnumSingle.java的代码如下:发现这个构造器其实是有参的,因此可以修改构造器的参素,尝试反编译枚举。 所以通过反射尝试破坏单例的正确程序为:
import java.lang.reflect.Constructor; public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws Exception { EnumSingle instance = EnumSingle.INSTANCE; //就只是修改了构造器这儿里 Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); //java.lang.NoSuchMethodException: com.ph.single.EnumSingle.<init>() System.out.println(instance); System.out.println(instance2); } }