今天听完狂神说java的单例模式,感慨万分。希望通过这篇文章跟大家一起学习。 [1] 一个私有的构造器 饿汉式单例是直接使用静态变量的方式生成这个单例对象(不管是否调用) , 所以缺点比较明显就是占用空间。 懒汉式:表示使用的时候再去创建对象,又叫做DCL(Double Check Lock即双重检测锁模式) 注意:最后送大家十套2020最新Java架构实战教程+大厂面试题库,进裙 783802103 在裙文件下载一起交流进步哦! <2> 看一看这个单例其实已经很完美了,可是我们却忘记了反射这个操作,在反射面前似乎又并不安全。 结果:这时你会发现单例模式被破坏了,因为生成了两个不同的对象 结果:很nice,确实做到防止反射破坏单例了,这时我又觉得自己行了。 结果:gg, 还有这么赖的方式创建对象,那我们的单例不是又被破坏了吗 结果:观察两个对象的hash值,确实是单例安全的。 说明:这个方式实现的单例和前面的懒汉式和饿汉式都是线程不安全的 注意:最后送大家十套2020最新Java架构实战教程+大厂面试题库,进裙 783802103 在裙文件下载一起交流进步哦! 单例模式的这4种实现方式,本质上各有各的优势。其中我们使用懒汉式的单例时,一般只要实现到避免指令重排那一步即可。因为深究反射可以让类中的方法和属性都透明化。(后面关于反射的优化很多只是为了面试时能多说一点)使用饿汉式的单例时,虽然占用内存,不过实现简单。
前言
1. 饿汉式
[2] 一个静态变量
[3] 一个静态方法返回对象package com.gs.juc.单例模式; //饿汉式单例 public class Hungry { //可能会造成浪费空间(假设此时有相应的变量生成) //private byte[] data1 = new byte[1024*1024]; // 1. 一个私有的构造器 private Hungry(){ } //2.一个静态变量 private final static Hungry HUNGRY = new Hungry(); //3.一个静态方法 public static Hungry getInstance(){ return HUNGRY; } }
2. 懒汉式
public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+" ok"); } private static LazyMan lazyMan; //双重检测锁模式(DCL双重检测锁模式) public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ //多一层判断是防止多个线程同时进入了第一个判断,导致生成多个对象 if(lazyMan==null){ lazyMan = new LazyMan(); //不是一个原子操作 } } } return lazyMan; }
<1> 那么我们就会有疑问了,这种方式在多线程的环境下就已经安全了吗?
其实并不然,因为这个lazyMan = new LazyMan();的操作并不是一个原子操作,它底层是分为三步去执行的。
[1] 分配内存空间
[2] 执行构造方法,初始化对象
[3] 把这个对象指向内存空间
我们期待的执行顺序当然是[1][2][3],可是计算机底层为了提高效率,存在一种指令重排的操作。在不影响这个操作最终结果的前提下,它可以对这三个执行步骤进行重新排序,也就是它可能会变成[1][3][2],那这种情况会带来什么影响呢?如果我们在多线程的环境下执行,当A线程执行了[1] [3] 操作后,B线程进来了,因为这时lazyMan已经有相应的对象了,那么B线程不会走判断的步骤,而是直接返回 lazyMan这个对象,可是A线程并没有初始化完成,这时返回的就是一个空对象。
解决方法:为了防止指令重排其实很简单,就是在变量上加上一个 volatile关键字(volatile可以通过内存屏障防止指令重排)
只需改变上面的一行代码即可: private volatile static LazyMan lazyMan;
public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+" ok"); } private static LazyMan lazyMan; //双重检测锁模式(DCL双重检测锁模式) public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ //多一层判断是防止多个线程同时进入了第一个判断,导致生成多个对象 if(lazyMan==null){ lazyMan = new LazyMan(); //不是一个原子操作 } } } return lazyMan; } //-------------在上面的基础上增加的代码----------- //通过反射破坏单例 public static void main(String[] args) throws Exception { //我们先通过单例的方法获取一个实例 LazyMan instance = LazyMan.getInstance(); //再通过一个反射获取另外一个实例 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //设置权限 declaredConstructor.setAccessible(true); LazyMan instance1 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance1); } }
解决方式:可以在构造器的时候,引入一个判断(三重判断), 检查lazyMan这个对象是否已经存在 public class LazyMan { // -------和上面代码有所区别的地方--------- private LazyMan() { synchronized (LazyMan.class){ if(lazyMan==null){ System.out.println(Thread.currentThread().getName() + " ok"); }else{ throw new RuntimeException("不要试图通过反射破坏单例"); } } } private static LazyMan lazyMan; //双重检测锁模式(DCL双重检测锁模式) public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ //多一层判断是防止多个线程同时进入了第一个判断,导致生成多个对象 if(lazyMan==null){ lazyMan = new LazyMan(); //不是一个原子操作 } } } return lazyMan; } //通过反射破坏单例 public static void main(String[] args) throws Exception { //我们先通过单例的方法获取一个实例 LazyMan instance = LazyMan.getInstance(); //再通过一个反射获取另外一个实例 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //设置权限 declaredConstructor.setAccessible(true); LazyMan instance1 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance1); } }
<3> 可是如果细想,如果两个实例都通过反射获得,那么自然不会存在lazyMan这个判断的阻挠。我们把代码往下改: public class LazyMan { private LazyMan() { synchronized (LazyMan.class){ if(lazyMan==null){ System.out.println(Thread.currentThread().getName() + " ok"); }else{ throw new RuntimeException("不要试图通过反射破坏单例"); } } } private static LazyMan lazyMan; //双重检测锁模式(DCL双重检测锁模式) public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ //多一层判断是防止多个线程同时进入了第一个判断,导致生成多个对象 if(lazyMan==null){ lazyMan = new LazyMan(); //不是一个原子操作 } } } return lazyMan; } //----------与上面有所区别的地方--------- public static void main(String[] args) throws Exception { //两个对象都通过反射获得 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //设置权限 declaredConstructor.setAccessible(true); LazyMan instance1 = declaredConstructor.newInstance(); LazyMan instance = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance1); } }
解决方式:我们在学习并发编程的时候,学过一种信号灯的方式,简单的说就是设置一个标志位flag为false,当有线程走了LazyMan()这个私有化构造器先判断这个标志位,若为false,则放行,并把标志位置为ture。下次有线程进来就直接被拦截了。 public class LazyMan { // --------- 和上面有所区别的地方------------ //设置一个标志位 private static boolean flag = false; private LazyMan() { synchronized (LazyMan.class) { //优化4:通过标志位去避免两次反射创建对象的情况 if (flag == false) { flag = true; System.out.println(Thread.currentThread().getName()+" ok"); } else { throw new RuntimeException("不要试图破坏反射"); } } } private static LazyMan lazyMan; //双重检测锁模式(DCL双重检测锁模式) public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ //多一层判断是防止多个线程同时进入了第一个判断,导致生成多个对象 if(lazyMan==null){ lazyMan = new LazyMan(); //不是一个原子操作 } } } return lazyMan; } //----------与上面有所区别的地方--------- public static void main(String[] args) throws Exception { //两个对象都通过反射获得 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //设置权限 declaredConstructor.setAccessible(true); LazyMan instance1 = declaredConstructor.newInstance(); LazyMan instance = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance1); } }
我好像又可以了,可是反射就不能获得你标志位的属性吗?停,刚精快醒醒。别试了,在反射面前所有的属性和方法都是透明,所以理论上都是不安全的。在java中有一个类天生就是单例安全的,没错它就是枚举类。为什么它安全呢?因为它可以避免反射的操作。
反射中newInstance() 上对枚举类的说明:
3. 枚举类的单例
package com.gs.juc.单例模式; import java.lang.reflect.Constructor; import java.lang.reflect.Method; //枚举是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws Exception { //1.通过枚举 EnumSingle instance1 = EnumSingle.INSTANCE; EnumSingle instance2 = EnumSingle.INSTANCE; System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } }
4. 静态内部类实现单例模式
package com.gs.juc.单例模式; //静态内部类 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } }
小结:
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算