男 【23种设计模式专题】一 单例模式,谁说程序猿没有女朋友

程序猿学社的,欢迎Star
技术专题
本文已记录到
文章目录双重校验锁DCL(第三种)2.0版本,线程安全静态内部类(第四种)枚举(第五种)
前言
你问程序猿小哥哥,有对象吗
他会毫不犹豫的告诉你,有,简单呀 , new一个呗 。
我们知道"对象"只会有一个,毕竟,不是谁都是韦小宝 , 有7个老婆,可以new 7次 。
那怎么保证只会new出一个对象,这就是本文的主角单例模式 。
现在这年代太难了,找对象难,在程序中 , new一个对象,还这么讲究 。
概念
采取一定的手段,保证一个类中只有一个实例,并且只提供一个取到该实例的入口 。
应用场景
总结:
如果该对象频繁的被使用,我们就可以考虑把他设计单例 。
实战篇 new两个对象,判断是否相等
package com.cxyxs.designmode.util;/*** Description:* Author: 程序猿学社* Date:2020/3/15 12:30* Modified By:*/public class Demo1 {public static void main(String[] args) {Girlfriend gf = new Girlfriend();Girlfriend gf1 = new Girlfriend();System.out.println(gf== gf1);}}classGirlfriend{}
各位社友 , 觉得输出的结果是什么?true还是false
饿汉式(第一种)
package com.cxyxs.designmode.util;/*** Description:* Author: 程序猿学社* Date:2020/3/15 13:15* Modified By:*/public class Demo2 {public static void main(String[] args) {Girlfriend2 gf1 = Girlfriend2.getInstance();Girlfriend2 gf2 = Girlfriend2.getInstance();System.out.println(gf1 == gf2);}}classGirlfriend2{//第二步privatestatic Girlfriend2 gf = new Girlfriend2();// 第一步private Girlfriend2() {}//第三步publicstatic Girlfriend2 getInstance(){returngf;}}
输出的值为true , 说明只实例化一次 。我们来梳理一下代码实现思路 。
在学习单例过程中 , 我们会经常看到一个词汇“懒加载” , 他是什么意思?
总结:
饿汉式是通过变量,也就是类加载原理保证单例,也就是所谓的线程安全 。没有实现懒加载 。
个人的建议:
可以使用,内存浪费的问题,几乎可以忽略,在实际开发过程中,存在一个类,也不调用 , 还傻傻的放在那里,说明这个代码,可能是无用的代码,对于 , 没有的代码,直接干掉 。
懒汉式(第二种)
package com.cxyxs.designmode.util;/*** Description:懒汉式* Author: 程序猿学社* Date:2020/3/15 14:00* Modified By:*/public class Demo3 {public static void main(String[] args) {Girlfriend3 gf = Girlfriend3.getInstance();Girlfriend3 gf1 = Girlfriend3.getInstance();System.out.println(gf == gf1);}}classGirlfriend3{private staticGirlfriend3 gf =null;private Girlfriend3() {}public static Girlfriend3 getInstance(){if(gf == null){gf = new Girlfriend3();}return gf;};}
打印的结果也为true,各位社友 , 是不是觉得,这就实现单例?
实际上,这种写法是有问题的,会存在线程安全的问题
模拟线程不安全
package com.cxyxs.designmode.util;import java.util.concurrent.TimeUnit;/*** Description:懒汉式,模拟线程不安全* Author: 程序猿学社* Date:2020/3/15 14:00* Modified By:*/public class Demo4 {public static void main(String[] args) {new Thread(()->{Girlfriend4 gf = Girlfriend4.getInstance();System.out.println(gf);}).start();new Thread(()->{Girlfriend4 gf1 = Girlfriend4.getInstance();System.out.println(gf1);}).start();}}classGirlfriend4{private staticGirlfriend4 gf =null;private Girlfriend4() {}public staticGirlfriend4 getInstance(){if(gf == null){try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}gf = new Girlfriend4();}return gf;};}
对多线程不了解社友,看到线程不安全这个词汇,可能会有点懵逼,这里我简单的阐述一下,如何界定一个线程是否安全 。
模拟线程不安全,方法加
跟上面的方法相比 , 只是增加了关键字
package com.cxyxs.designmode.util;import java.util.concurrent.TimeUnit;/*** Description:懒汉式,方法上增加同步* Author: 程序猿学社* Date:2020/3/15 14:00* Modified By:*/public class Demo5 {public static void main(String[] args) {new Thread(()->{Girlfriend5 gf = Girlfriend5.getInstance();System.out.println(gf);}).start();new Thread(()->{Girlfriend5 gf1 = Girlfriend5.getInstance();System.out.println(gf1);}).start();}}classGirlfriend5{private staticGirlfriend5 gf =null;private Girlfriend5() {}public static synchronizedGirlfriend5 getInstance(){if(gf == null){try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}gf = new Girlfriend5();}return gf;};}
总结:
通过关键字,解决懒汉式单例线程安全的问题,虽说能保证线程安全,但是,效率太低,在实际项目实战过程中,不建议使用这种方式 。
隔壁老王:社长,那有什么办法,可以保证线程安全,效率还行的实现方式吗?
社长:有的,通过双重校验锁方式 。
双重校验锁DCL(第三种) 1.0版本 , 线程不安全
package com.cxyxs.designmode.util;import java.util.concurrent.TimeUnit;/*** Description:懒汉式 双重校验锁DCL* Author: 程序猿学社* Date:2020/3/15 14:00* Modified By:*/public class Demo6 {public static void main(String[] args) {new Thread(()->{Girlfriend6 gf = Girlfriend6.getInstance();System.out.println(gf);}).start();new Thread(()->{Girlfriend6 gf1 = Girlfriend6.getInstance();System.out.println(gf1);}).start();}}classGirlfriend6{private staticGirlfriend6 gf =null;private Girlfriend6() {}public staticGirlfriend6 getInstance(){if(gf == null){//步骤1synchronized (Girlfriend6.class){if(gf == null){gf = new Girlfriend6();//步骤2}}}return gf;};}
这样就会存在问题,假设,他的顺序是1-3-2,线程A刚刚跑到1-3这一步,把gf指向一个地址的时候(表示gf这个对象不为空,因为已经有内存地址) , 而这时的线程B , 刚刚到步骤1,进行对应的判断,发现gf不为空 , 直接退出程序 。
题外话 , 浅谈指令重排
package com.cxyxs.designmode.util;/*** Description:* Author: wude* Date:2020/3/18 11:29* Modified By:*/public class Test {public static void main(String[] args) {Test test = new Test();}}
idea版本中,在类里面右键,保证该类已运行,不然,会提示找不到主类的错 。
隔壁老王:社长 , 既然多线程环境下,会存在指令重排的问题,是不是说通过双重校验锁这种方式实现单例 , 也不可行 。
社长:既然存在指令重排的问题,jdk大佬当然也考虑这个问题,增加关键字
2.0版本,线程安全
总结:
通过双重检验锁 , 很好的解决解决线程安全的问题 。建议使用 。实现了懒加载 。
静态内部类(第四种)
package com.cxyxs.designmode.util;/*** Description:* Author: 程序猿学社* Date:2020/3/18 19:59* Modified By:*/public class Demo7 {public static void main(String[] args) {Girlfriend7 gf = Girlfriend7.getInstance();Girlfriend7 gf1 = Girlfriend7.getInstance();System.out.println(gf == gf1);}}classGirlfriend7{private Girlfriend7() {}public staticGirlfriend7 getInstance(){returnInstance.gf;}privatestaticclass Instance{private static final Girlfriend7 gf = new Girlfriend7();}}
总结:
静态内部类通过 机制来保证单例和线程安全 。同时也是懒加载的 , 只有使用到该静态内部类被调用时,才会被加载 。
隔壁老王:社长,社长 , 听说反射可以破坏单例,具体是怎么一回事?
社长:既然,你提了这个问题 , 我们就来具体看看,为什么反射可以破坏单例 。
反射破坏单例
实际上 , 不止反射可以破坏单例,通过序列化的方式也可以破坏单例 。
package com.cxyxs.designmode.util;import java.lang.annotation.Annotation;import java.lang.reflect.Constructor;/*** Description:模拟通过反射破坏单例* Author: 程序猿学社* Date:2020/3/19 9:15* Modified By:*/public class Demo8 {public static void main(String[] args) throws Exception {Constructor declaredConstructor = Girlfriend8.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);//暴力访问Girlfriend8 gf = declaredConstructor.newInstance();Girlfriend8 gf1 = declaredConstructor.newInstance();System.out.println(gf);System.out.println(gf1);}}classGirlfriend8{private Girlfriend8() {}public staticGirlfriend8 getInstance(){returnInstance.gf;}privatestaticclass Instance{private static final Girlfriend8 gf = new Girlfriend8();}}
隔壁老王:社长,那怎么解决反射可以暴力破解单例的问题?
社长:在构造方法里面,再判断一下这个对象是否为空 。
防止反射,增加监控代码
package com.cxyxs.designmode.util;import java.lang.annotation.Annotation;import java.lang.reflect.Constructor;/*** Description:模拟通过反射破坏单例* Author: 程序猿学社* Date:2020/3/19 19:15* Modified By:*/public class Demo8 {public static void main(String[] args) throws Exception {Constructor declaredConstructor = Girlfriend8.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);//暴力访问Girlfriend8 gf = declaredConstructor.newInstance();Girlfriend8 gf1 = declaredConstructor.newInstance();System.out.println(gf);System.out.println(gf1);}}classGirlfriend8{private Girlfriend8() {synchronized (Girlfriend8.class){if(Instance.gf == null){try {throw new Exception("该对象已实例化,不要试图破解!");}catch (Exception e){e.printStackTrace();}}}}public staticGirlfriend8 getInstance(){returnInstance.gf;}privatestaticclass Instance {private static final Girlfriend8 gf = new Girlfriend8();}}
枚举(第五种)
package com.cxyxs.designmode.util;import java.lang.reflect.Constructor;/*** Description:* Author: 程序猿学社* Date:2020/3/20 19:55* Modified By:*/public enumDemo9 {GF;public Demo9 getInstance(){return GF;}}classTest9{public static void main(String[] args) throws Exception{Demo9 instance = Demo9.GF.getInstance();Demo9 instance1 = Demo9.GF.getInstance();System.out.println(instance == instance1);}}
隔壁老王:社长,为什么枚举可以解决别人暴力破解的问题?
社长: 给你看一段源码,你就知道为什么枚举可以防止别人暴力破解 。
还记得我在上一个事例中,给你说过,通过反射可以单例,写了一段代码 , 我们来看看源码 。
最近有不少读者在问我java应该如何学习,在这里,把我整理的学习视频分享出来 。
(1).,视频
【男【23种设计模式专题】一 单例模式,谁说程序猿没有女朋友】(2).架构师视频,设计模式视频 , 深入jvm内核原理 。
(3) java面试视频
可以通过公众号“程序猿学社”,回复关键字"视频",希望能帮到你 。
原创不易,不要白嫖,觉得有用的社友,给我点赞,让更多的老铁看到这篇文章 。