文章目录
-
在学习23设计模式之前,需要先了解OOP思想:
-
23设计模式分类
4.1、单例模式(深度挖掘)
- 什么是单例模式?
- 使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
- 私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
饿汉式:
-
package 设计模式.单例模式; //饿汉式 public class Hungry { //将类中所有内存资源全部加载进来。 private byte data[]= new byte[1024*1024]; private byte data1[]= new byte[1024*1024]; private byte data2[]= new byte[1024*1024]; private byte data3[]= new byte[1024*1024]; //私有构造器 private Hungry(){ } //私有静态变量,在初始化时为其分配内存空间,直接加载 private final static Hungry HUNGRY=new Hungry(); //获取私有静态变量 public static Hungry getInstance(){ return HUNGRY; } }
-
优缺点:
- 优点:线程安全,采取直接实例化 getnstance 的方式就不会产生线程不安全问题。
- 缺点:直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。非常占用内存资源。
懒汉式
-
package 设计模式.单例模式; //懒汉模式 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+"ok"); } private static LazyMan LAZY_MAN; public static LazyMan getInstance(){ if (LAZY_MAN==null){ LAZY_MAN=new LazyMan(); } return LAZY_MAN; /* *懒汉模式在单线程下开单例模式是的确ok的,但是在多线程下呢?? * */ } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
-
多次实验结果:
-
我们发现多线程下懒汉式是不安全的,单例模式是不成功的。
- 具体分析:
- 我们发现在为其初始化对象时不满足原子性
- 1.分配内存空间
- 2.初始化对象(执行构造方法)
- 3.把这个对象指向这个内存空间
- 因此容易造成在底层会造成指令重排的现象。
DCL懒汉式(双重校验锁-线程安全)
-
package 设计模式.单例模式; //懒汉模式 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+"ok"); } private volatile static LazyMan LAZY_MAN; //加入双重校验锁,该方法又称为DCL懒汉式,在设计多线程时需要满足单例模式 public static LazyMan getInstance(){ if(LAZY_MAN==null){ synchronized (LazyMan.class){ if (LAZY_MAN==null){ LAZY_MAN=new LazyMan(); } } } return LAZY_MAN; /* *懒汉模式在单线程下开单例模式是的确ok的,但是在多线程下呢?? * */ } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); /* * 我们发现多线程下懒汉式是不安全的,单例模式是不成功的。 * 具体分析: * 我们发现在为其初始化对象时不满足原子性 * 1.分配内存空间 * 2.初始化对象(执行构造方法) * 3.把这个对象指向这个内存空间 * 因此在底层会造成指令重排的现象。 * * */ } } }
-
实验结果:
-
懒汉式的优缺点:
- 优点:节约资源,只有在使用该类的时候才会为其分配资源
- 缺点:懒汉式在多线程模式下是不安全的,如果多个线程能够同时进入
if(LAZY_MAN==null)
,并且此时LAZY_MAN==null
,那么会有多个线程执行对象初始化现象。进而引发并发问题。 - 解决办法:加入锁机制。
思考问题:懒汉式的单例模式一定安全吗?
-
答案是肯定的,只要是人为设计的代码,就一定会有缺陷。我们下面将通过反射破坏DCL懒汉式。
-
那么什么是反射?
- 反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法;
- 这就说明反射能通过具体实例化对象拿去类的私有化的静态变量,那么这样就会引发安全问题。
-
package 设计模式.单例模式; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; //懒汉模式 public class LazyMan { private LazyMan(){ } private volatile static LazyMan LAZY_MAN; public static LazyMan getInstance(){ if(LAZY_MAN==null){ synchronized (LazyMan.class){ if (LAZY_MAN==null){ LAZY_MAN=new LazyMan(); } } } return LAZY_MAN; /* *懒汉模式在单线程下开单例模式是的确ok的,但是在多线程下呢?? * */ } public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //无视私有构造器。 declaredConstructor.setAccessible(true); //通过反射创建实例化对象 LazyMan instance2 = declaredConstructor.newInstance(); //查看是否破坏了单例模式 System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } }
-
实验结果:
-
-
因此我们发现懒汉式的单例模式被破坏了,实例化了两个对象。
-
-
解决办法:同理进入私有构造器加锁机制。
-
package 设计模式.单例模式; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; //懒汉模式 public class LazyMan { private LazyMan(){ synchronized (LazyMan.class){ throw new RuntimeException("不要试图通过反射破坏异常"); } } private volatile static LazyMan LAZY_MAN; public static LazyMan getInstance(){ if(LAZY_MAN==null){ synchronized (LazyMan.class){ if (LAZY_MAN==null){ LAZY_MAN=new LazyMan(); } } } return LAZY_MAN; /* *懒汉模式在单线程下开单例模式是的确ok的,但是在多线程下呢?? * */ } public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //第一个对象进入私有构造器会进行判断。 LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //无视私有构造器。 declaredConstructor.setAccessible(true); //通过反射创建实例化对象 LazyMan instance2 = declaredConstructor.newInstance(); //查看是否破坏了单例模式 System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } }
-
因此后面我们又引入枚举。