单例模式
核心作用,保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
单例模式的优点
由于单例模式值生成一个实例,减少了系统开销,当一个对象的产生需要比较多大资源的时候,如读取配置文件,产生其他依赖对象,则可以通过在应用启动时直接产生一个对象,然后永久驻留内存.单例模式可以在系统设置全局的访问点,优化共享资源访问.
常见5中实现:
- 饿汉式(线程安全,效率高,但是,不能延时加载)
- 懒汉式(线程安全,效率不高,但是可以延时加载)
- 双重检测锁(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
- 静态内部类(线程安全,调用效率高,但是,可以延时加载)
- 枚举单例(线程安全,调用效率高,不能延时加载)
饿汉式
public class SingleDemo {
//类加载直接创建实例,类加载时是线程安全的,所以getSingle不需要同步
private static SingleDemo single = new SingleDemo();
//但是模式私有构造器
private SingleDemo() {}
//保证了对象的单例
public static SingleDemo getSingle() {
return single;
}
}
懒汉式
public class SingleDemo {
//先不创建对象
private static SingleDemo single;
//但是模式私有构造器
private SingleDemo() {}
//只有在调用方法,确保single是null没有创建在new对象,为了线程安全有 synchronized 所以效率不高,但是可以延时加载
public static synchronized SingleDemo getSingle() {
if(single == null) {
single = new SingleDemo();
}
return single;
}
}
双重检测锁
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
线程 1 进入 getInstance() 方法。
由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
线程 1 被线程 2 预占。
线程 2 进入 getInstance() 方法。
由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
线程 2 被线程 1 预占。
线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance。
线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。
线程 1 被线程 2 预占。
线程 2 获取 //1 处的锁并检查 instance 是否为 null。
由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。
双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。但在JDK1.5后可以通过 volatile 修饰来解决但这也是不推荐的
静态内部类
当初始化类的时候,并不会立即初始化它的静态内部类,只有用的时候才初始化,兼备并发高效延时加载,推荐使用
public class SingleDemo {
//单实例作为静态内部类的成员变量
private static class SingleDemoInstance{
private static final SingleDemo single = new SingleDemo();
}
//只有在调用方法,确保single是null没有创建在new对象,为了线程安全有 synchronized 所以效率不高,但是可以延时加载
public static SingleDemo getSingle() {
return SingleDemoInstance.single;
}
//但是模式私有构造器
private SingleDemo() {}
}
枚举单例模式
枚举对象天然是单例的,每个枚举元素都是一个实例,枚举可以避免反射,反序列化漏洞创建对象(没有延时加载)
public enum SingleEnum {
//枚举元素本身就是一个实例,天然的单例
INSTANCE;
//像用其他类一种添加功能处理,
public void operation() {
}
}
执行效率
从优到次
饿汉式<静态内部类<枚举<双重检测锁<懒汉式