单例模式---懒汉式的优化

    技术2025-04-18  10

    单例模式中最重要的思想是:构造器私有,因此能保证我们的内存中只有一个对象。

    单例模式分为懒汉式和饿汉式

    1.饿汉式:顾名思义,很饿,上来就吃。

    package cn.com; //饿汉式单例,在程序运行时,无论是否需要 都会创建对象,因此 可能会浪费空间 public class HungryMan { //构造器私有,无法new 这个对象 private HungryMan() { } private static HungryMan HUNGRY = new HungryMan(); private static HungryMan getInstance() { return HUNGRY; } //测试后 得到的 对象的hashcode 一致,说明是同一个对象 public static void main(String[] args) { HungryMan instance1 = HungryMan.getInstance(); HungryMan instance2 = HungryMan.getInstance(); HungryMan instance3 = HungryMan.getInstance();   // 打印hashcode System.out.println(instance1); System.out.println(instance1); System.out.println(instance1); } } 运行结果: 三个对象的hashcode 值一样,说明是指向了同一个对象 cn.com.Hungry@1b6d3586 cn.com.Hungry@1b6d3586 cn.com.Hungry@1b6d3586

    2.懒汉式单例

    package cn.com; public class LazyMan1 { //构造器私有 private LazyMan1() { System.out.println(Thread.currentThread().getName() + " 正在运行!"); } //先不创建 ,等使用的时候再去创建 private static LazyMan1 man; public static LazyMan1 getInstance() { if (man == null) { man = new LazyMan1(); } return man; } /** * 此代码是线程不安全的,只在单线程下可以 * 例如 */ public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { LazyMan1.getInstance(); }).start(); } } } 运行结果

    Thread-0  正在运行! Thread-3  正在运行! Thread-2  正在运行! Thread-1  正在运行!

    单例模式下,存在多个线程,明显不符合单例模式要求。下面对代码进行改进

    3.懒汉式单例 改进

    注: new LazyMan();不是原子性操作,实际底层有三步,

    1.分配内存空间 

    2.执行构造方法,初始化对象

     3.把对象指向这个空间 

    当A线程执行方法时,可能在执行 1---3---2 ;如果此时B线程进入,由于A线程已经将空对象指向内存空间,因此B将会得到一个null的对象。所以,必须使用volatile  修饰符 避免 被指令重排

    package cn.com; //懒汉式单例 public class LazyMan { //构造器私有 private LazyMan() { System.out.println(Thread.currentThread().getName() + "is ok "); } //volatile 避免被指令重排 private volatile static LazyMan lanzMan; //双重检测锁 模式的懒汉式单例 ----DCL 懒汉式 public static LazyMan getInstance() { if (lanzMan == null) { synchronized (LazyMan.class) { if (lanzMan == null) { lanzMan = new LazyMan(); } } } return lanzMan; } //多线程并发 public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } } 多次运行结果均为Thread-0is ok ;说明在多线程下是安全的。

    4.尽管我们使用了双重检测锁,但是由于反射的存在,上述代码仍然是不安全的。例如,我们通过反射机制来破坏单例模式。

    package cn.com; import java.lang.reflect.Constructor; //懒汉式单例 public class LazyMan { //构造器私有 private LazyMan() { System.out.println(Thread.currentThread().getName() + "is ok "); } //volatile 避免被指令重排 private volatile static LazyMan lanzMan; //双重检测锁 模式的懒汉式单例 ----DCL 懒汉式 public static LazyMan getInstance() { if (lanzMan == null) { synchronized (LazyMan.class) { if (lanzMan == null) { lanzMan = new LazyMan(); /** * new LazyMan();不是原子性操作; * 1.分配内存空间 * 2.执行构造方法,初始化对象 * 3.把对象指向这个空间 */ } } } return lanzMan; } //由于反射的存在,即使 使用双重检测锁 ,在多线程下也是不安全的 public static void main(String[] args) throws Exception { //实例1 LazyMan lazyMan1 = LazyMan.getInstance(); /** * 通过反射破坏单例模式 */ //获取空参构造器 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true);// 可以无视私有构造器 LazyMan lazyMan2 = declaredConstructor.newInstance(); System.out.println(lazyMan1.hashCode()); System.out.println(lazyMan2.hashCode()); } } 运行结果

    460141958 1163157884

    由此可见,单例模式下 我们获取的是两个不同的对象。

    解决办法:在构造器加synchronized,又双重检测锁升级为三重检测锁

    //构造器私有 private LazyMan() { synchronized (LazyMan.class) { if (lanzMan != null) { throw new RuntimeException("不要来搞破坏"); } } }

    再执行代码,结果:

     

    上述解决办法可行吗?再来。。。

    我们将获取对象的方式全改为 反射获取

    //由于反射的存在,即使 使用双重检测锁 ,在多线程下也是不安全的 public static void main(String[] args) throws Exception { Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true);// 可以无视私有构造器 LazyMan lazyMan1 = declaredConstructor.newInstance(); LazyMan lazyMan2 = declaredConstructor.newInstance(); System.out.println(lazyMan1.hashCode()); System.out.println(lazyMan2.hashCode()); }

    执行结果:  发现 又获取到了不同对象。。。。。

    解决办法:通过在构造器增加标志位

    package cn.com; import sun.nio.cs.FastCharsetProvider; import java.lang.reflect.Constructor; //懒汉式单例 public class LazyMan { private static boolean str = false; //构造器私有 private LazyMan() { synchronized (LazyMan.class) { if (str == false) { str = true; } else { throw new RuntimeException("不要来搞破坏"); } } } private volatile static LazyMan lanzMan; public static LazyMan getInstance() { if (lanzMan == null) { synchronized (LazyMan.class) { if (lanzMan == null) { lanzMan = new LazyMan(); } } } return lanzMan; } public static void main(String[] args) throws Exception { //获取空参构造器 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true);// 可以无视私有构造器 LazyMan lazyMan1 = declaredConstructor.newInstance(); LazyMan lazyMan2 = declaredConstructor.newInstance(); System.out.println(lazyMan1.hashCode()); System.out.println(lazyMan2.hashCode()); } } 执行结果,问题已解决。 我想说,,,其实这种方法仍然是不安全的,后面讲如何通过枚举类解决此问题

    Processed: 0.009, SQL: 9