Swift一些收藏产品

设计模式-创建者模式-单例模式

2022-05-10  本文已影响0人  石头耳东

零、 本文纲要

  1. 饿汉式
    ① 静态变量方式
    ② 静态代码块
    ③ 枚举
  2. 懒汉式
    ① 静态方法(线程不安全)
    ② 静态synchronized方法(线程安全)
    ③ 静态方法双检锁(线程不安全)
    ④ 静态方法双检锁volatile优化(线程安全)
    ⑤ 静态内部类(线程安全)
  1. 防止序列化破坏单例
  2. 防止反射破坏单例

java.lang.Runtime类

一、 单例模式

1. 饿汉式

Ⅰ 私有构造private Singleton() {}

Ⅱ 静态变量private static Singleton instance = new Singleton();

Ⅲ 静态方法获取单例public static Singleton getInstance() { return instance; }

/**
 * 饿汉式
 * 静态变量创建类的对象
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance = new Singleton();

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

Ⅰ 私有构造

Ⅱ 静态变量

Ⅲ 静态代码块赋值

Ⅳ 静态方法获取对象

/**
 * 饿汉式
 * 在静态代码块中创建该类对象
 */
public class Singleton {

    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}
/**
 * 枚举方式
 */
public enum Singleton {
    INSTANCE;
}

2. 懒汉式

/**
 * 懒汉式
 * 线程不安全
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

存在问题:

Ⅰ 多线程下单例难以保证

sequenceDiagram
participant t1 as Thread1
participant t2 as Thread2
t1->t1:instance == null
t1->>+t2:Thread1时间片耗尽,Thread2使用CPU
t2->t2:instance == null<br>instance = new Singleton()<br>return instance
t2->>-t1:Thread2使用完CPU,Thread1分配使用
t1->t1:instance = new Singleton()<br>return instance
静态方法并发问题.png

上图中不难看出,由于上下文切换的原因,本应单例的实例被创建了多次。

Ⅱ 多线程下安全问题

 6: new           #3                  // class com/stone/Singleton
 9: dup
10: invokespecial #4                  // Method "<init>":()V
13: putstatic     #2                  // Field instance:Lcom/stone/Singleton;

new指令:在java堆上为com/stone/Singleton对象分配内存空间,并将地址压入操作数栈顶;

dup指令:复制操作数栈顶值,并将其压入栈顶,此时操作数栈上有连续相同的两个对象地址;

invokespecial指令:需要从操作数栈顶弹出一个this引用,来调用实例初始化方法"<init>":()V,这一步会弹出一个之前入栈的对象地址;

putstatic指令:将对象地址赋值给静态变量Singleton instance。

sequenceDiagram
participant t1 as Thread1
participant t2 as Thread2
t1->t1:new<br>dup<br>putstatic
t1->>+t2:Thread1时间片耗尽,Thread2使用CPU
t2->t2:instance == null不成立<br>直接返回instance
t2->>-t1:Thread2使用完CPU,Thread1分配使用
t1->t1:invokespecial
指令重排异常.png

由于发生了指令重排,instance未完成构造但被返回,故而出错。

/**
 * 懒汉式
 *  线程安全
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    //对外提供静态方法获取该对象
    public static synchronized Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

由于synchronized是添加在方法上,多线程并发时会有争抢,性能有损耗。

/**
 * 双重检查方式
 */
public class Singleton { 

    //私有构造方法
    private Singleton() {}

    private static Singleton instance;

   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为null
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

synchronized关键字保证了原子性、可见性、有序性。

但是这里的有序性是保证acquire barrier到release barrier内的指令不能和外部的指令重排,即我们monitor所管理的指令与外部指令不发生重排。

10: monitorenter
11: getstatic     #2                  // Field instance:Lcom/stone/Singleton;
14: ifnonnull     27
17: new           #3                  // class com/stone/Singleton
20: dup
21: invokespecial #4                  // Method "<init>":()V
24: putstatic     #2                  // Field instance:Lcom/stone/Singleton;
27: aload_0
28: monitorexit
29: goto          37
32: astore_1
33: aload_0
34: monitorexit

所以,其内部指令还是存在重排的可能。则会与静态方法(线程不安全)的案例发生一样的线程安全问题,出现空指针的异常。

/**
 * 双重检查方式
 */
public class Singleton {

    //私有构造方法
    private Singleton() {}

    private static volatile Singleton instance;

   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为空
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

当Thread1执行到volatile关键字修饰的instance变量的操作时,此处为instance = new Singleton();操作,则会在该操作上方添加一个写屏障,该操作下方添加一个读屏障。写屏障保证操作上方的写操作不能越过屏障重排,读屏障保证操作下方的读操作不能越过屏障重排。则Thread2要么读到null竞争锁,要么等instance = new Singleton();操作完成,读到生成的instance对象。

/**
 * 静态内部类方式
 */
public class Singleton {

    //私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

二、 单例模式问题解决

1. 防止序列化破坏单例

重写readResolve方法,如下:

public class Singleton implements Serializable {

    //私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    /**
     * 下面是为了解决序列化反序列化破解单例模式
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

2. 防止反射破坏单例

在构造方法内做非空判断,如下:

public class Singleton {

    //私有构造方法
    private Singleton() {
        /*
           反射破解单例模式需要添加的代码
        */
        if(instance != null) {
            throw new RuntimeException();
        }
    }
    
    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

三、 JDK中存在的单例模式

Ⅰ 私有构造

Ⅱ 静态变量

Ⅲ 静态方法获取

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }
    
    private Runtime() {}
    ... ...
}

四、 结尾

以上即为设计模式-创建者模式-单例模式的全部内容,感谢阅读。

上一篇 下一篇

猜你喜欢

热点阅读