设计模式-单例模式(上)

120 阅读3分钟

1、什么是单例模式?

“确保一个类只有一个实例,并且提供一个访问它的全局方法。”

2、常见使用实例

jdk Runtime类

image.png

该类用于管理应用程序运行期间的各种信息,比如内存信息、环境变量等。

3、单例模式的两种类型及代码实现

3.1 饿汉式

在类初始化时,就进行了实例化。很多人认为饿汉式的缺点是在启动过程中就初始化完成,占用资源大。但是从另一方面来想,如果这个初始化耗时长,在我们的接口使用它的时候才去初始化,那么也会造成接口响应时间过长。所以这也不能完全算是缺点,还是需要权衡。

3.1.1 标准饿汉

// 方式一 实例是公有的,可直接通过Singleton.instance使用
public static class Singleton {

    public static final Singleton instance = new Singleton();

    private Singleton() {
    }
}

// 方式二 实例是私有的,对外提供公有的getInstance()方法来获取实例
public static class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

推荐使用第二种方式。

因为第二种方式,更加灵活

假如我们现在有一个需求,要求该类为每一个线程生成一个实例,那么使用方式二我们就可以保留getInstance()这个方法,只需要修改方法的具体实现,更加灵活。

3.1.2 枚举类(饿汉单例最推荐使用的,原因在后面分析)

创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)。

public enum Single {
        SINGLE;

        private Single() {
        }

        public void print() {
            System.out.println("hello world");
        }
    }

3.2 懒汉式

在第一次调用的时候进行实例化

3.2.1 简单单线程

当instance为null时,多个线程同时进入判断,导致会创建多个实例。

    public static Singleton1 getInstanceA() {
        if (null == instance) {
            instance = new Singleton1();
        }
        return instance;
    }

3.2.2 加synchronized

多线程环境下能保证创建一个实例,但是效率低。

public static synchronized Singleton1 getInstanceB() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }

3.2.3 双重校验锁

第一个if判断是为了提高效率的,如果实例已经被创建了,那么就不需要加锁。

// 增加volatile关键字,防止指令重排序
private static volatile Singleton1 singleton1;

public static Singleton1 getInstanceC() {
        // 先判断实例是否存在,若不存在再对类对象进行加锁处理
        if (instance == null) {
            synchronized (Singleton1.class) {
                if (instance == null) {
                    instance = new Singleton1();
                }
            }
        }
        return instance;
    }

3.2.4 静态内部类

class SingletonStaticInnerSerialize{
	
	
	private static class InnerClass {
		private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();
	}
	
	public static SingletonStaticInnerSerialize getInstance() {
		return InnerClass.singletonStaticInnerSerialize;
	}
}	
    

3.2.5 防止反序列化创建多实例的静态内部类

在反序列化时,若目标类有readResolve方法,那就通过反射的方式调用要被反序列化的类中的readResolve方法,返回一个对象。否则就创建一个对象。

//使用匿名内部类实现单例模式,在遇见序列化和反序列化的场景,得到的不是同一个实例
//解决这个问题是在序列化的时候使用readResolve方法
class SingletonStaticInnerSerialize implements Serializable {
	
	private static final long serialVersionUID = 1L;
	
	private static class InnerClass {
		private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();
	}
	
	public static SingletonStaticInnerSerialize getInstance() {
		return InnerClass.singletonStaticInnerSerialize;
	}
	// 关键!!!!
	protected Object readResolve() {
		return InnerClass.singletonStaticInnerSerialize;
	}
}

3.3 总结与对比

初始化时机是否线程安全是否支持延迟加载
标准饿汉类加载期间
枚举类加载期间
简单单线程第一次使用时
synchronized第一次使用时
双重校验锁第一次使用时
静态内部类这个内部类在其静态成员被调用时发生

推荐使用'枚举'、'双重校验锁'、'静态内部类'这三种方式。