彻底玩转单例模式

HeJin大约 5 分钟JavaJUC并发编程

饿汉式单例

/**
 * @Desc 饿汉式单例
 * @Author HeJin
 * @Date 2021/3/13 10:58
 */
public class Hungry {
    private Hungry(){
    }
    
    private final static Hungry hungry = new Hungry();
    
    public static Hungry getInstance(){
        return hungry;
    }
}

懒汉式单例 - 单线程

/**
 * @Desc 懒汉式单例
 * @Author HeJin
 * @Date 2021/3/13 11:02
 */
public class LazyMan {
    private LazyMan(){
    }
    
    private static LazyMan lazyMan;
    
    public static LazyMan getInstance(){
        if (lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

单线程下这个程序单例是可以的。多线程就会出问题,单例会被破坏。

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + " Ok");
    }
    
    private static LazyMan lazyMan;
    
    public static LazyMan getInstance(){
        if (lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(LazyMan::getInstance).start();
        }
    }
}

结果:

Thread-1 Ok
Thread-3 Ok
Thread-4 Ok
Thread-2 Ok
Thread-0 Ok
    
Process finished with exit code 0

单例被破坏了。

DCL懒汉式

双重检测锁模式的懒汉式单例,也叫DCL懒汉式。

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) {
        for (int i = 0; i < 10; i++) {
            new Thread(LazyMan::getInstance).start();
        }
    }
}

结果:

Thread-0 Ok
    
Process finished with exit code 0

这样比直接同步方法性能更高,如果已有对象,无须进入同步代码块。

完整的DCL懒汉式

/**
* new创建对象不是原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*/
lazyMan = new LazyMan();

期望的顺序是1、2、3。假设A线程指令重排执行了1、3、2,那么B线程就会直接返回lazyMan。但是此时lazyMan还没有完成初始化构造。

完整的双重检测锁懒汉式单例

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + " Ok");
    }
    
    // 禁止指令重排
    private volatile static LazyMan lazyMan;
    
    /**
     * 双重检测锁模式的懒汉式单例 DCL懒汉式
     */
    public static LazyMan getInstance(){
        if (lazyMan == null){
            synchronized (LazyMan.class){
                if (lazyMan == null){
                    /**
                     * 不是原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     * 有可能发生指令重排
                     * 期望123
                     * 132
                     */
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(LazyMan::getInstance).start();
        }
    }
}

静态内部类单例

public class Holder {
    private Holder(){
    }
    
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    
    static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

这个静态内部类单例也是不安全的,通过反射可以破坏单例。

反射破坏DCL懒汉式单例

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + " Ok");
    }

    private volatile 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 instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        // 无视私有private
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

结果:

main Ok
main Ok
com.hejin.single.LazyMan@14ae5a5
com.hejin.single.LazyMan@7f31245a
    
Process finished with exit code 0

从结果可以发现instance1 和instance2不是同一个对象。从构造方法打印的结果也可以判断,创建了两次。单例被破坏。

在构造方法里加锁判断。

private LazyMan(){
    synchronized (LazyMan.class){
        if (lazyMan != null){
            throw new RuntimeException("不要试图使用反射破坏单例");
        }
    }
    
    System.out.println(Thread.currentThread().getName() + " Ok");
}

结果:

main Ok
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.hejin.single.LazyMan.main(LazyMan.java:42)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏单例
at com.hejin.single.LazyMan.<init>(LazyMan.java:14)
... 5 more
    
Process finished with exit code 1

再次破坏。两个对象都通过反射创建。

public static void main(String[] args) throws Exception {
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    // 无视私有private
    declaredConstructor.setAccessible(true);
    LazyMan instance1 = declaredConstructor.newInstance();
    LazyMan instance2 = declaredConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);
}

结果:

main Ok
main Ok
com.hejin.single.LazyMan@14ae5a5
com.hejin.single.LazyMan@7f31245a
    
Process finished with exit code 0

发现单例又被破坏了。

使用红绿灯

private static boolean fla = false;
private LazyMan(){
    synchronized (LazyMan.class){
        if (fla == false){
            fla = true;
        } else {
            throw new RuntimeException("不要试图使用反射破坏单例");
        }
    }
    System.out.println(Thread.currentThread().getName() + " Ok");
}

结果:

main Ok
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.hejin.single.LazyMan.main(LazyMan.java:47)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏单例
at com.hejin.single.LazyMan.<init>(LazyMan.java:18)
... 5 more
    
Process finished with exit code 1

反射破坏标志位的值

public static void main(String[] args) throws Exception {
    // 假设已经知道了红绿灯标志位字段
    Field fla = LazyMan.class.getDeclaredField("fla");
    fla.setAccessible(true);
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    // 无视私有private
    declaredConstructor.setAccessible(true);
    LazyMan instance1 = declaredConstructor.newInstance();
    fla.set(instance1, false);
    LazyMan instance2 = declaredConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);
}

结果:

main Ok
main Ok
com.hejin.single.LazyMan@7f31245a
com.hejin.single.LazyMan@6d6f6e28
    
Process finished with exit code 0

单例被破坏。


道高一尺,魔高一丈。


分析Constructor源码。newInstance。

image-20210325164532882
image-20210325164532882

枚举类型

枚举是JDK1.5出来的,自带单例模式。

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) {
        EnumSingle instance = EnumSingle.INSTANCE;
        EnumSingle instance1 = EnumSingle.INSTANCE;
        System.out.println(instance);
        System.out.println(instance1);
    }
}

结果:

INSTANCE
INSTANCE
    
Process finished with exit code 0

分析源码

image-20210325164508232
image-20210325164508232

反射获取枚举

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
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);
    }
}

结果:

Exception in thread "main" java.lang.NoSuchMethodException: com.hejin.single.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.hejin.single.Test.main(EnumSingle.java:20)
    
Process finished with exit code 1

错误显示没有空参的构造器。正常错误应该是: image-20210325164425313

IDEA骗了我们。

自己手动反编译class文件

// javap -p EnumSingle.class
Compiled from "EnumSingle.java"
    public final class com.hejin.single.EnumSingle extends java.lang.Enum<com.hejin.single.EnumSingle> {
        public static final com.hejin.single.EnumSingle INSTANCE;
        private static final com.hejin.single.EnumSingle[] $VALUES;
        public static com.hejin.single.EnumSingle[] values();
        public static com.hejin.single.EnumSingle valueOf(java.lang.String);
        // 空参构造
        private com.hejin.single.EnumSingle();
        public com.hejin.single.EnumSingle getInstance();
        static {};
    }

说明这个代码也骗了我们。

使用jad工具反编译

https://varaneckas.com/jad/open in new window

jad.zipopen in new window

>jad -sjava EnumSingle.class
Parsing EnumSingle.class... Generating EnumSingle.java

当前目录下多了一个EnumSingle.java文件。打开文件:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   EnumSingle.java
package com.hejin.single;
public final class EnumSingle extends Enum
{
    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }
    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/hejin/single/EnumSingle, name);
    }
    // 有参构造器
    private EnumSingle(String s, int i)
    {
        super(s, i);
    }
    public EnumSingle getInstance()
    {
        return INSTANCE;
    }
    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];
    static
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

修改反射代码

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

结果:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.hejin.single.Test.main(EnumSingle.java:22)
    
Process finished with exit code 1

符合预期的错误。 image-20210325164400873