单例模式
概念
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有某个对象被创建;而且还提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
保证一个类只有一个实例:如果你创建了一个对象,同时过一会后决定再创建一个新对象,此时会获得之前已创建的对象,而不是一个新对象。注意:普通构造函数无法实现上述行为,因为构造函数的设计决定了它必须总是返回一个新对象
为该实例提供一个全局访问节点:和全局变量一样,单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。
优点
- 只生成一个实例,减少了系统的性能开销和内存开支
(目的可能为了控制某些共享资源(如数据库或文件)的访问权限 - 可以避免对资源的多重占用
- 可以在系统设置全局的访问点,优化和共享资源访问
缺点
- 单例模式一般没有接口,扩展困难,若要扩展,除了修改代码基本上没有第二种途径可以实现
- 若单例模式没有实现,是不能进行测试的
- 单例类的职责过重,在一定程度上违背了“单一职责原则”
结构类图
创建方式
- 饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
- 懒汉式:类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
- 静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
- 枚举单例: 使用枚举实现单例模式。优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
- 双重检测锁方式 (因为JVM本质重排序的原因,可能会初始化多次,不推荐使用)
如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类好于懒汉式。 最好使用饿汉式
实现
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。
一、懒汉式
synchronized
将整个 getInstance() 方法设为同步(synchronized)。不高效,因为任何时候只能有一个线程调用getInstance() 方法,而同步操作只需要在第一次创建单例实例对象时才需要。
public class Singleton{
private static Singleton instance;
// 加 synchronized 保证只有一个线程进入方法
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
双重检验锁模式DCL
同步块加锁
public class Singleton{
// 声明成 static volatile
private static volatile Singleton instance;
// 私有,使外界构造方法失效
private Singleton(){
}
// 声明成 static
public static Singleton getInstance(){
// 非空时直接返回
if(instance == null){
synchronized(Singleton.class){
// 防止生成多个实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
第一个if非空直接返回,减少synchronized浪费性能
第二个if防止生成多个实例
懒汉式 静态内部类
public class Singleton{
// 静态内部类只会被加载一次,故 instance 单例且线程安全
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
// getInstance()方法调用时才加载静态内部类
public static final Singleton getInstance(){
return SingletonHolder.instance;
}
}
二、饿汉式
- static:第一次加载类到内存中时就会初始化
- final :保持引用变量instance指向值不变
public class Singleton{
// 饿汉式
private static final Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
三、枚举
public class Singleton{
/**
*枚举类型是线程安全的,并且只会装载一次
*/
public enum SingletonEnum{
INSTANCE;
private final Singleton instance;
SingletonEnum(){
instance = new Singleton();
}
private Singleton getInstance(){
return instance;
}
}
public static Singleton getInstance(){
return SingletonEnum.INSTANCE.getInstance();
}
}