累成一条狗

单例模式也能玩出花

一、单例模式

1、什么是单例模式

(1)单例模式

【单例模式(Singleton Pattern):】
定义:
    Ensure a class has only one instance, and provide a global point of access to it.
    直译:确保一个类只有一个实例,并提供对它的全局访问点(只允许通过全局访问点获取实例对象)。

 

(2)单例模式实现要点

一般情况下,访问类中某变量、方法:
    可以通过 new 进行对象实例化,再通过 "对象名.变量名"、"对象名.方法名" 的形式获取。
    可以通过 static 修饰(全局)变量、方法,再通过 "类名.变量名"、"类名.方法名" 的形式获取。可避免使用 new 进行对象实例化。

为了保证一个类只存在一个实例,应该保证其有且只有 一次 实例化 机会:
    应该保证其构造方法不能在该类以外的地方被调用(防止使用 new 进行对象实例化)。
    构造方法只能在该类中被调用一次。
注:
    对象实例化常见方式: new、序列化、克隆、反射。
    
基本实现要点:
    构造方法私有化(防止使用 new 进行对象实例化)。
    在类的内部进行一次实例化(构造方法只能在该类中被调用一次)。
    对外提供一个全局访问点(全局变量、全局方法等),可以通过 "类名.变量名" 或者 "类名.方法名" 的形式获取实例对象(避免使用 new 进行对象实例化)。
注:
    反射会破坏 构造方法的私有化,需要注意,后面会介绍。
    序列化、克隆 等操作可能会破坏单例模式。需要注意。

 

(3)使用场景
  当频繁创建、销毁某个对象时,可以考虑单例模式。
  当创建对象消耗资源过多时,但又经常使用时,可以考虑单例模式。

 

2、常见单例模式实现方式

(1)实现方式

【饿汉式:】
    静态变量
    静态代码块
    枚举(推荐)

【懒汉式:】
    静态方法
    synchronized 同步方法
    synchronized 同步代码块
    双重检查
    静态内部类

 

(2)饿汉式、懒汉式 区别

【基本区别:】
    懒汉式 在需要使用对象的时候才进行实例化操作。
    饿汉式 在类加载时完成实例化操作,可能暂时还不用该对象(占用内存)。

【饿汉式:】
核心:
    饿汉式借助 JVM 的类加载机制,在 类加载的初始化阶段 完成 实例化操作。
    类初始化阶段 只会执行一次,从而保证实例的唯一性 以及 线程安全。
    当类被主动使用时,才会导致类的初始化。而被动使用时,不会导致类的初始化。
主动使用类的方式:
    类的 main 方法被调用时。
    执行 new 实例化操作时。
    访问静态变量、静态方法时。
    实例化子类时(先触发父类初始化)。
    反射调用某类时。
JVM 类加载过程可参考: https://www.cnblogs.com/l-y-h/p/13496969.html#_label1_5

【懒汉式:】
核心:
    在需要使用对象的时候才进行实例化操作。
    多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。

 

二、饿汉式

1、实现

(1)基本说明

【核心思路:】
    使用 static 关键字,借助类加载过程,进行实例的初始化。
    使用 private 修饰 构造方法,保证构造方法私有化。
    提供一个全局访问点(类名.变量名 或者 类名.方法名)获取对象。    
 
【可用方式:】
    静态变量
    静态方法
    静态代码块

【优点:】
    在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。
    
【缺点:】
    在类加载的初始化阶段完成了实例化,没有实现懒加载(Lazy Loading),可能造成内存的浪费(在不需要使用的时候被创建)。

 

(2)代码实现(静态变量)
  public 修饰变量,直接通过 "类名.变量名" 的方式获取对象。

class HungrySingleton {
    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton singleton = new HungrySingleton();

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
    }
}

public class Test {
    public static void main(String[] args) {
        HungrySingleton singleton = HungrySingleton.singleton;
        HungrySingleton singleton2 = HungrySingleton.singleton;
        System.out.println(singleton == singleton2);  // true,为同一个对象
    }
}

 

(3)代码实现(静态方法)
  private 修饰变量,不允许通过 "类名.变量名" 的形式访问。
  public 修饰方法,通过 "类名.方法名" 的方式获取对象。

class HungrySingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton = new HungrySingleton();

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System.out.println(singleton == singleton2);  // true,为同一个对象
    }
}

 

(4)代码实现(静态代码块)
  静态代码块,只是将实例化操作 移动到 静态代码块中进行实现。

class HungrySingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
    static {
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System.out.println(singleton == singleton2);  // true,为同一个对象
    }
}

 

(5)这就完了吗?
  当然不是了,这样写只是防止了通过 new 实例化对象。
  对象实例化的方式还有 反射、序列化、克隆 等操作。
  这些操作是否会破坏单例模式?需要思考一下。

 

2、反射破坏

(1)类主动使用时,才会进行类的初始化
  类只有主动使用时,才会进行初始化操作。并不一定使用到类,就会触发初始化操作。
比如:
  进行反射获取私有构造方法时,并不会触发 类加载过程。
  如下代码执行后,静态代码块中的 "start..." 不会输出。

import java.lang.reflect.Constructor;

class HungrySingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
    static {
        System.out.println("start...");
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
        Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true);
    }
}

 

(2)反射破坏
  如下代码所示,反射调用构造方法时,会进行类加载过程(输出 "start..." ),然后构建一个实例。
  此时的实例对象是通过 构造方法重新创建的对象。与类加载过程中创建的对象不同。
  即 反射对 单例模式造成了破坏。

import java.lang.reflect.Constructor;

class HungrySingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
    static {
        System.out.println("start...");
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
        Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true); // 此时,还不会触发 类加载过程
        HungrySingleton singleton = hungrySingletonConstructor.newInstance(); // 此时,触发 类加载过程,并创建一个实例
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System.out.println(singleton == singleton2);  // false,不为同一个对象
     }
}

 

(3)防止反射破坏(未必会生效)
  在构造方法中,判断实例是否已经被创建。
  类初始化过程中,会创建一个实例。即使通过反射调用构造方法,也会在实例创建之后再去调用,所以在 构造方法中进行判断,实例存在则会抛出异常。从而防止反射破坏(未必会生效,后续序列化破坏中有提到)。

import java.lang.reflect.Constructor;

class HungrySingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
    static {
        System.out.println("start...");
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
        if (singleton != null) {
            throw new RuntimeException("实例已存在,不允许重复创建");
        }
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
        Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true);
        HungrySingleton singleton = hungrySingletonConstructor.newInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System.out.println(singleton == singleton2);  // true,为同一个对象
    }
}

 

3、反序列化破坏

(1)序列化、反序列化
  序列化对象,并再次读取对象时(反序列化),会创建一个新的对象。
注:
  序列化就是把实体对象状态按照一定的格式写入到有序字节流。
  反序列化就是从有序字节流重建对象,恢复对象状态。

【反序列化核心代码:】
ObjectInputStream 中的 readOrdinaryObject() 方法

private Object readOrdinaryObject(boolean unshared) throws IOException {
    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(desc.forClass().getName(), "unable to create instance").initCause(ex);
    }

    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
        Object rep = desc.invokeReadResolve(obj);
        if (rep != obj) {
            handles.setObject(passHandle, obj = rep);
        }
    }
    return obj;
}

【关注点一:(调用构造函数实例化)】
desc.isInstantiable()
    如果一个 serializable/externalizable 的类可以在运行时被实例化,那么该方法就返回true。

desc.newInstance()
    通过反射调用无参构造创建一个对象。
注:
    此处调用的无参构造,与类本身的无参构造方法有差别。
    从实际效果上看,此处仅触发了类加载,并未触发类的构造函数。与前面提到的反射有区别。
    没有深入研究,有兴趣的可以帮忙解答一下。

【关注点二:(自定义对象生成策略)】
desc.hasReadResolveMethod()
    如果一个 serializable/externalizable 接口的类中包含 readResolve() 方法,则返回 true。

desc.invokeReadResolve(obj)
    通过反射的方式调用要被反序列化的类的 readResolve() 方法。

handles.setObject(passHandle, obj = rep)
    如果 readResolve() 返回的实例与构造方法创建的不同,则以 readResolve() 方法创建的实例为准。

 

(2)反序列化破坏
  如下代码所示,通过反序列化创建了个对象。
  从实际代码执行结果看,反序列化仅触发了类加载过程(此时调用了构造函数),反序列化中 newInstance() 未主动触发类的构造函数,所以此处构造方法中的判断 无法防止 反序列化中反射的行为。
  即 反序列化对 单例模式造成了破坏。

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

class HungrySingleton implements Serializable {
    private static final long serialVersionUID = 42L;

    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
        if (singleton != null) {
            throw new RuntimeException("实例已存在,不允许重复创建");
        }
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
//        oos.writeObject(HungrySingleton.getInstance());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        HungrySingleton singleton = (HungrySingleton) ois.readObject();

        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System.out.println(singleton == singleton2); // false,不为同一对象
    }
}

 

(3)防止反序列化破坏
  通过 readResolve() 可以返回一个实例对象,保证此对象为类加载过程中创建的实例对象,即可防止 反序列化破坏。

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

class HungrySingleton implements Serializable {
    private static final long serialVersionUID = 42L;

    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
        if (singleton != null) {
            throw new RuntimeException("实例已存在,不允许重复创建");
        }
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }

    // 定义 readResolve() 方法,返回类加载过程中创建的实例对象(反序列化时返回此对象)
    private Object readResolve() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
//        oos.writeObject(HungrySingleton.getInstance());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        HungrySingleton singleton = (HungrySingleton) ois.readObject();

        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System.out.println(singleton == singleton2); // true,为同一对象
    }
}

 

4、克隆破坏

(1)克隆破坏
  如下代码所示,通过克隆创建了个对象。
  调用了 Object 的 clone 方法(native 方法),与反序列化类似,也没有触发 类的构造方法(应该是直接从内存中 copy 了一份)。创建了一个新的对象。
  即 克隆对 单例模式造成了破坏。

class HungrySingleton implements Cloneable {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
        if (singleton != null) {
            throw new RuntimeException("实例已存在,不允许重复创建");
        }
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }

    // 重写 clone() 方法,返回 clone 对象
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
        System.out.println(singleton == singleton2); // false,不是同一对象
    }
}

 

(2)防止克隆破坏
  保证 clone() 方法返回的对象为类加载过程中创建的实例对象,即可防止 克隆破坏。

class HungrySingleton implements Cloneable {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    // 构造器私有化(防止通过new创建实例对象)
    private HungrySingleton() {
        if (singleton != null) {
            throw new RuntimeException("实例已存在,不允许重复创建");
        }
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static HungrySingleton getInstance() {
        return singleton;
    }

    // 重写 clone() 方法,返回 clone 对象
    @Override
    public Object clone() throws CloneNotSupportedException {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
        System.out.println(singleton == singleton2); // true,是同一对象
    }
}

 

5、枚举

(1)基本说明

【基本说明:】
    写个简单的 enum 类,然后反编译一下 javap -c xx.class。
    可以看到底层就类似于 饿汉式 静态代码块 的写法。在类加载的初始化阶段完成实例化操作。

【优点:】
    在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。
    可以防止 克隆、反序列化、反射 破坏单例模式。
    写法简单。

 

(2)反编译一下 enum 类

【EnumSingleton】
enum EnumSingleton {
    INSTANCE;
}

【javap -c EnumSingleton.classfinal class pattern.sington.EnumSingleton extends java.lang.Enum<pattern.sington.EnumSingleton> {
  public static final pattern.sington.EnumSingleton INSTANCE;

  public static pattern.sington.EnumSingleton[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lpattern/sington/EnumSingleton;
       3: invokevirtual #2                  // Method "[Lpattern/sington/EnumSingleton;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lpattern/sington/EnumSingleton;"
       9: areturn

  public static pattern.sington.EnumSingleton valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class pattern/sington/EnumSingleton
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class pattern/sington/EnumSingleton
       9: areturn

  static {};
    Code:
       0: new           #4                  // class pattern/sington/EnumSingleton
       3: dup
       4: ldc           #7                  // String INSTANCE
       6: iconst_0
       7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #9                  // Field INSTANCE:Lpattern/sington/EnumSingleton;
      13: iconst_1
      14: anewarray     #4                  // class pattern/sington/EnumSingleton
      17: dup
      18: iconst_0
      19: getstatic     #9                  // Field INSTANCE:Lpattern/sington/EnumSingleton;
      22: aastore
      23: putstatic     #1                  // Field $VALUES:[Lpattern/sington/EnumSingleton;
      26: return
}

【等价于:】
public final class EnumSingleton extends Enum< EnumSingleton> {
    public static final EnumSingleton INSTANCE;
    public static EnumSingleton[] values();
    public static EnumSingleton valueOf(String s);
    static {
        INSTANCE = new EnumSingleton(name, ordinal);
    };
}

 

(3)防止反射破坏
  枚举类型的类,没有无参构造。默认继承 Enum 的有参构造。

【代码实现:】
import java.lang.reflect.Constructor;

enum EnumSingleton {
    INSTANCE;
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
        Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor(String.class, int.class);
        enumSingletonConstructor.setAccessible(true);
        // newInstance 会出现异常,java.lang.IllegalArgumentException: Cannot reflectively create enum objects
        EnumSingleton enumSingleton = enumSingletonConstructor.newInstance();  

        EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE;

        System.out.println(enumSingleton == enumSingleton2);
    }
}

【原因分析:】
newInstance() 方法中进行判断,若为枚举类型,则抛异常。

@CallerSensitive
public T newInstance(Object ... initargs) throws IllegalArgumentException
{
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
}

 

(4)防止克隆破坏
  枚举类型的类,无法重写 clone() 方法。其父类 Enum 中定义 clone() 方法为 final 类型,不能被子类重写。

protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

 

(5)防止序列化破坏
  序列化返回的是同一个对象,无需定义 readResolve() 方法。其执行的是另一个逻辑。

【代码实现:】
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

enum EnumSingleton {
    INSTANCE;
}

public class Test {
    public static void main(String[] args) throws Exception {
        EnumSingleton enumSingleton = EnumSingleton.INSTANCE;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(enumSingleton);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        EnumSingleton enumSingleton2 = (EnumSingleton) ois.readObject();

        System.out.println(enumSingleton == enumSingleton2); // true,是同一个对象
    }
}

【反序列化核心代码:】
ObjectInputStream 中的 readEnum() 方法。
读入并返回枚举常量,如果枚举类型不可解析,则返回 nullprivate Enum<?> readEnum(boolean unshared) throws IOException {

    ObjectStreamClass desc = readClassDesc(false);

    int enumHandle = handles.assign(unshared ? unsharedMarker : null);
    
    String name = readString(false);
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            Enum<?> en = Enum.valueOf((Class)cl, name);
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }

    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}

 

三、懒汉式

1、实现

(1)基本说明

【核心思路:】
    使用 private 修饰 构造方法,保证构造方法私有化。
    提供一个静态的公共方法,在调用该方法时,才去创建实例对象。(全局访问点,通过 "类名.方法名" 获取对象)。    
 
【可用方式:】
    静态方法
    synchronized 同步方法
    synchronized 同步代码块
    双重检查
    静态内部类

【优点:】
    懒加载,需要使用对象时才会去实例化操作,提高内存利用率。

【缺点:】
    多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。

 

(2)代码实现(静态方法)
  如下代码所示,只允许通过 "类名.方法名" 的方式获取对象。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,通过 "类名.变量名" 访问
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            fullSingleton = new FullSingleton();
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FullSingleton fullSingleton = FullSingleton.getInstance();
        FullSingleton fullSingleton2 = FullSingleton.getInstance();
        System.out.println(fullSingleton == fullSingleton2); // true,是同一个对象
    }
}

 

(3)这就完了吗?
  当然不是了,这样写只是在单线程环境下正常执行。多线程操作下,会出现多个实例。
比如:
  线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,执行 new 实例化操作,此时便会产生多个实例对象。
  如下代码所示,代码执行多次可以发现,两个线程输出的对象并不一致。
  此时单例模式被破坏,线程不安全。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            fullSingleton = new FullSingleton();
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@f3f9f4b
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@26af6bb1
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(4)代码实现(synchronized 同步方法)
  为了保证线程安全,可以使用 synchronized 关键字实现同步。
注:
  synchronized 保证同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
  如下代码所示,在方法上添加一个 synchronized,代码执行多次可以发现,两个线程输出的对象始终一致。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
    public static synchronized FullSingleton getInstance() {
        if (fullSingleton == null) {
            fullSingleton = new FullSingleton();
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(5)这就完了吗?
  当然不是了,虽然使用 synchronized 保证线程安全,但是这种方式锁粒度太大,可能会导致执行效率低。

(6)代码实现(synchronized 同步代码块)
  如下代码所示,为了缩小 synchronized 影响范围,可以在方法内部使用同步代码块的方式实现。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            synchronized(FullSingleton.class) {
                fullSingleton = new FullSingleton();
            }
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@5eb39c2b
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@401a5cff
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(7)这就完了吗?
  当然不是了,这样写又回到了 静态方法中 提到的 线程不安全的问题上了。
比如:
  线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,遇到 synchronized,同步执行后,仍会执行 new 操作,产生多个实例对象。
  此时单例模式被破坏,线程不安全。双重检查可以解决这个问题。

 

2、双重检查

(1)代码实现
  如下代码所示,双重检查,在 synchronized 同步代码块 的基础上,再添加一个判断。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            synchronized(FullSingleton.class) {
                if (fullSingleton == null) {
                    fullSingleton = new FullSingleton();
                }
            }
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance());
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance());
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(2)这就完了吗?
  当然不是了。这样写看上去是保证了线程安全,但是有个细节需要思考一下(指令重排)。
  如下所示,反编译一下代码,可以看到实例化操作的相关指令。

【Test.java】
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
    }
}

【javap -c Test.classpublic class pattern.sington.Test {
  public pattern.sington.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class pattern/sington/Test
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: return
}

【关注 main 函数:】
    new 指令在 堆内存中为 Test 对象分配内存空间。
    invokespecial 指令,执行实例初始化操作。
    astore_1 指令,将栈顶引用类型值存入变量(即 使对象指向 堆内存空间)。
即分为三步:
    1、分配内存空间。
    2、实例初始化
    3、实例指向内存空间
注:
    按照常理说,1、2、3 是按照顺序执行的。
    但是 JVM 会根据处理器特性,对指令进行优化(指令重排序),从而提高性能。
    指令重排,意味着指令可能不会按照指定顺序执行。
    
【回到上例的 双重检查的代码:】
    发生指令重排,new 实例化操作按照 1、3、2 的顺序执行。
假设线程 A 执行完 1、3,但 2 还未执行完,即对象已指向内存空间,但是还没有初始化。
此时线程 B 执行 getInstance() 代码,由于对象已指向内存空间,判断对象是否为 null 时返回 false, 跳过 synchronized 代码块。
此时线程 B 拿到的实例对象,由于初始化并未完成,使用对象将可能出现错误(引用逃逸)。
注: synchronized 并非原子性操作,可能发生指令重排。 使用 voliate 可以通过 内存屏障 禁止指令重排序。

 

(3)代码实现(voliate )
  使用 voliate 修饰 变量,禁止指令重排序。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    // volatile 防止指令重排
    private static volatile FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            synchronized(FullSingleton.class) {
                if (fullSingleton == null) {
                    fullSingleton = new FullSingleton();
                }
            }
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c
            }
        });

        thread.start();
        thread2.start();
    }
}

 

3、静态内部类

(1)基本说明
  静态内部类是一种结合了 饿汉模式、懒汉模式 优点的实现方式。

【核心思路:】
    使用 private 修饰 构造方法,保证构造方法私有化。
    在类的内部定义一个静态内部类(只有被调用时,才会被加载),并在内部类中实例化对象。
    提供一个静态的公共方法,在调用该方法时,调用静态内部类。(全局访问点,通过 "类名.方法名" 获取对象)。    
 
【优点:】
    定义内部类,只有在用到的时候才回去加载,实现懒加载。
    使用 static 定义内部类,利用 JVM 类加载机制保证 线程安全。

 

(2)代码实现
  如下代码所示,定义一个静态内部类。

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    // 定义静态内部类
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(3)这就完了吗?
  当然不是了。反序列化破坏、反射破坏、克隆破坏 的问题同样存在。
  解决方式与 饿汉模式的解决方式类似。

(4)防止序列化破坏
  重写 readResolve() 方法,返回实例对象。

import java.io.*;

class FullSingleton implements Serializable {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    // 定义静态内部类
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }

    private Object readResolve() {
        return getInstance();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FullSingleton fullSingleton = FullSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(fullSingleton);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        FullSingleton fullSingleton2 = (FullSingleton) ois.readObject();
        System.out.println(fullSingleton == fullSingleton2);
    }
}

 

(5)防止克隆破坏
  重写 clone() 方法,返回实例对象。

class FullSingleton implements Cloneable {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
    }

    // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    // 定义静态内部类
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return getInstance();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FullSingleton fullSingleton = FullSingleton.getInstance();
        FullSingleton fullSingleton2 = (FullSingleton) fullSingleton.clone();
        System.out.println(fullSingleton == fullSingleton2);
    }
}

 

(6)防止反射破坏
  在构造方法中,新增一个判断。

import java.lang.reflect.Constructor;

class FullSingleton {
    // 私有化变量,不可以通过 "类名.变量名" 的形式访问
    private static FullSingleton fullSingleton;

    // 构造器私有化(防止通过new创建实例对象)
    private FullSingleton () {
        if (getInstance() != null) {
            throw new RuntimeException("实例已存在,创建失败");
        }
    }

    // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    // 定义静态内部类
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<FullSingleton> fullSingletonClass = FullSingleton.class;
        Constructor<FullSingleton> fullSingletonConstructor = fullSingletonClass.getDeclaredConstructor();
        fullSingletonConstructor.setAccessible(true);
        FullSingleton fullSingleton = fullSingletonConstructor.newInstance();

        FullSingleton fullSingleton2 = FullSingleton.getInstance();
        System.out.println(fullSingleton == fullSingleton2);
    }
}

 

四、举例

1、JDK中的单例模式举例(Runtime)

(1)部分源码
  如下代码所示,就是 饿汉模式的 实现。

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

 

posted on 2021-11-19 19:52  累成一条狗  阅读(717)  评论(0编辑  收藏  举报

导航