单例模式

单例模式

1.饿汉模式

类加载的时候,就进行对象的创建,系统开销较大,但是不存在线程安全

* 饿汉模式-单例对象构建方法
* (类加载的时候,就进行对象的创建,系统开销较大,影响性能,所以多数采用饿汉模式,在使用时才真正的创建单例对象)
方式一:饿汉模式:性能最差
class Singleton1 {
	private static Singleton1 singleton = new Singleton1(); // 建立对象
	
问题:每次类加载的时候,就进行对象的创建,性能开销大,是一种空间换时间的方式


2.懒汉模式

多数采用饿汉模式,在使用时才真正的创建单例对象,但是存在线程安全

懒汉模式

线程不安全

方式二:懒汉模式-性能不安全
class Singleton2 {
	private static Singleton2 singleton = null; // 不建立对象
/*synchronized 可以解决线程安全问题,但是存在性能问题,即使singleton!=null也需要先获得锁*/
public synchronized static Singleton2 getInstance() {
	if (singleton == null) { // 先判断是否为空
		try {
			Thread.sleep(1000);
			System.out.println("构建这个对象可能耗时很长...");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		singleton = new Singleton2(); // 懒汉式做法
	}
	return singleton;
}
是一种时间换空间的方式

问题:每次线程都要走同步代码块性能 不高


线程安全doublecheck

方式三:懒汉模式-性能安全
class Singleton3 {
	private static Singleton3 singleton = null; // 不建立对象
	private Singleton3() {
	}
	/*synchronized 代码块进行双重检查,可以提高性能*/
	public static Singleton3 getInstance() {
		if (singleton == null) { // 先判断是否为空
			synchronized (Singleton3.class) {
				//此处必须进行双重判断,否则依然存在线程安全问题
				if(singleton == null){
					try {
						Thread.sleep(1000);
						System.out.println("构建这个对象可能耗时很长...");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					singleton = new Singleton3(); // 懒汉式做法
				}
			}
		}
		return singleton;
	}

这里的synchronize只有开头几个线程会进来,此时singleton为null。
为什么要进行双重判断?
因为同步代码块的存在,多个线程程序刚启动的时候会一起进来,1个线程进入代码块执行方法,结束后,其他线程执行方法前要判断这个对象是否为null,这里的判断是判断是否一初始化实例。
第一次是判断单例是否存在,准备使用
第二次是判断单例是否存在,准备创建

3.静态内部类单例

兼具懒汉模式和饿汉模式的优点

序列化方式和反射方式都可以破坏单例

定义饿汉类

public class HungrySingleton implements Serializable,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

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

1.序列化破坏单例

序列化的时候对于普通对象会new Instance

HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);

控制台输出:

单例模式

发现输出的是不同的地址,观察ois.readObject();的源码发现

因为对象是object的,所以走到

case TC_OBJECT:
    if (type == String.class) {
        throw new ClassCastException("Cannot cast an object to java.lang.String");
    }
    return checkResolve(readOrdinaryObject(unshared));

在这个readOrdinaryObject中发现代码片

try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {

观察isInstantiable()方法,说明返回的一定是true

/**
 * 如果表示的类是可序列化的,并且可以由序列化运行时实例化,则返回 true
 * Returns true if represented class is serializable/externalizable and can
 * be instantiated by the serialization runtime--i.e., if it is
 * externalizable and defines a public no-arg constructor, or if it is
 * non-externalizable and its first non-serializable superclass defines an
 * accessible no-arg constructor.  Otherwise, returns false.
 */
boolean isInstantiable() {
    requireInitialized();
    return (cons != null);
}

所以调用desc.newInstance(),这里就新建了一个实例,所以和原来的实例对象是不同的


拓展:序列化下实现单例

但观察源码,可以发现,如果object拥有readResolve()方法,则最后会返回这个方法的返回对象覆盖原来new Instance()对象,所以加上这个方法试试

修改HungrySingleton,在readResolve()方法中返回单例对象

public class HungrySingleton implements Serializable,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){
        return hungrySingleton;
    }

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

调试可得,发现输出的的确是单例

单例模式


2.反射方式破坏单例

懒汉可以采用防御机制,在无参构造方法中防御,但是懒汉模式无法防御

Class objectClass = LazySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton newInstance = (LazySingleton) constructor.newInstance();
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);

输出

单例模式

但是饿汉模式可以在无参构造器中加上防御机制

private HungrySingleton(){
    if(hungrySingleton != null){
        throw new RuntimeException("单例构造器禁止反射调用");
    }
}
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);

单例模式


3.使用枚举方式

针对序列化:

序列化的对象是确定的枚举值,所以一定是相同的

针对反射:

enum没有无参构造器

反射无法使用枚举!

通过反编译查看enum源码发现:

单例模式


4.匹配源码

饿汉模式:

单例模式


容器单例思想

单例模式

单例模式

上一篇:VS Code配置之选项卡多行显示


下一篇:Armv8-A虚拟化手册(1)