单例设计模式
概念:
一个类只有一个实例,并向整个系统提供这个实例。
优点:
- 由于单例模式只生成一个实例,减少了系统性能开销。
- 单例模式可以在系统设置全局的访问点,优化共享资源访问
常见的五种单例模式实现方式:
- 饿汉式 (线程安全,调用效率高,不能延时加载)
- 懒汉式 (线程安全,调用效率不高,可以延时加载)
- DLC懒汉式 (由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)
- 饿汉式改进:静态内部类式 (线程安全,调用效率高,可以延时加载)
- 枚举单例 (线程安全,调用效率高,不能延时加载)
步骤:
- 构造函数为私有,即
private
不对外开放。 - 通过静态方法(或枚举)返回单例对象。
- 确保单例的对象只能有一个,即使用
static
。
饿汉式:
在类加载时就初始化了,所以不能延时加载,且因为类加载时就初始化了所以线程是安全的。
// 饿汉式模式
class SingletonDemo1 {
// 1.私有化构造器
private SingletonDemo1() {
}
// 2.类初始化的时候,立即加载该对象
private static SingletonDemo1 instance = new SingletonDemo1();
// 3.提供获取该对象的方法,没有synchronized,效率更高
public static SingletonDemo1 getInstance() {
return instance;
}
}
懒汉式:
当用户第一次调用getInstance()
方法时才进行初始化,所以可以延时加载。使用synchronized
保证线程安全。
// 懒汉式模式
class SingletonDemo2 {
// 1.私有化构造器
private SingletonDemo2() {
}
// 2.类初始化的时候,不立即加载该对象
private static SingletonDemo2 instance;
// 3.提供获取该对象的方法,有synchronized,效率低
public static synchronized SingletonDemo2 getInstance() {
if(instance == null) instance = new SingletonDemo2();
return instance;
}
}
DLC懒汉式:
getInstance()
方法,有两次判空。第一层判断主要是为了避免不必要的同步,第二层判空是为了在null
情况下创建实例。由于JVM底层内部模型原因,偶尔会出现问题,不建议使用。
// DLC懒汉式模式
class SingletonDemo3 {
// 1.私有化构造器
private SingletonDemo3() {
}
// 2.类初始化的时候,不立即加载该对象
private volatile static SingletonDemo3 instance;
// 3.提供获取该对象的方法,有synchronized,效率低
public static SingletonDemo3 getInstance() {
if(instance == null){
synchronized(SingletonDemo3.class) {
if(instance == null){
instance = new SingletonDemo3();
}
}
}
return instance;
}
}
饿汉式改进:静态内部类实现
第一次加载SingletonDemo4
类时并不会初始化instance
,只有在第一次调用SingletonDemo4
的getInstance()
方法时才会导致instance
被初始化。因此,第一次调用getInstance()
方法会导致虚拟机加载InnerClass
内部类,内部类再初始化instance
。这种方法不仅能保证线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这也是一种推荐的单例模式实现方法 。
// 静态内部类实现
class SingletonDemo4 {
// 1.私有化构造器
private SingletonDemo4() {
}
// 2.静态内部类不持有外部类的引用,在使用时才加载
private static class InnerClass {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
// 3.提供获取该对象的方法,有synchronized,效率低
public static SingletonDemo4 getInstance() {
return InnerClass.instance;
}
}
枚举:
枚举在java种与普通类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建时线程安全的,并且在任何情况下都是一个单例。
public enum SingletonDemo5 {
INSTANCE;
public void doSomething() {
//do sth ...
}
}