设计模式之单例模式
定义
单例模式是最简单的设计模式之一,属于创建型模式,它提供了一种创建对象的方式,确保只有单个对象被创建。单例的三个特点:
- 类只能有一个实例
- 需要自行创建实例
- 需提供一个全局访问点
模式结构
- Singleton: 单例
代码示例
懒汉式(线程不安全型)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:延迟加载
缺点:线程不安全,只有一个线程访问的时候没什么问题,当多线程使用的时候可能就会出现线程A、线程B同时判断instance为空,因此就会创建多个实例。
懒汉式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为了解决懒汉式线程不安全的问题,可以使用synchronized将getInstance方法变为同步方法
优点:延迟加载、线程安全
缺点:每次调用getInstance方法的时候都要进行同步操作,但是多线程并发环境下不安全的问题只在第一次创建实例的时候才会出现,直接使用synchronized显得有点粗暴了,这样当一个线程执行getInstance方法,其它线程都会等待从而影响效率。这样就引出了双重锁校验模式。
双重锁校验(DCL,线程安全)
public class Singleton {
private static volatile Singleton instance;
private Singleton(){
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点:线程安全、只在第一次实例化的时候加锁提高了效率
缺点:volatile和锁还是有性能损耗,实现相对复杂
分析:第一次检查instance是否为null,如果不空直接返回,如果不空进入同步块。第二次检查instance是否为null,是为了防止在第一次检查的时候有多个线程同时检测到instance为null,然后会创建多个实例。
使用volatile修饰instance变量的原因:instance = new Singleton();这个语句创建对象的时候大致有三个步骤。
- 分配对象需要的内存空间
- 调用构造方法进行初始化
- 将instance指向分配的内存空间
如果不加volatile关键字,1、2、3可能会存在指令重排。如果执行顺序是1-3-2,在2还未执行、3执行完毕的时候如果有线程来进入到一次检查instance为空的地方,这个时候instance不为空会直接返回一个还未完成初始化的instance对象,导致出错。
饿汉式(线程安全)
public class Singleton {
private static (final)Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance
}
}
优点:简单、线程安全
缺点:不能懒加载,在第一次加载类到内存的时候就会初始化,不需要这个单例的时候也会被创建浪费内存资源
关于加不加final关键字取决于你需不需要释放资源?
如果存在资源释放的情况下就不能加final修饰;
为什么懒汉式不加final关键字?
final关键字要求在类加载的时候完成赋值,这样就变成了饿汉式了。
静态内部类(线程安全、懒汉式)
public class Singleton {
private Singleton(){
}
static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
优点:延迟加载、线程安全、性能高(不用加锁)
缺点:因为时延迟加载,第一次加载可能速度不够快,会被反序列化破坏
分析:依靠类的初始化保证线程安全、依靠内部类特性实现(内部类不使用不加载、并且只会在第一次使用时加载)懒加载
枚举单例(线程安全)
public enum Singleton {
INSTANCE;
}
优点:可以防止反射、反射反序列化破坏、性能好
优缺点
优点:
- 节约了系统资源。对于一些需要频繁创建和销毁的对象,使用单例可以避免系统资源消耗(创建大对象的性能损耗和频繁创建销毁对象面临的内存压力)
- 提供了对唯一实例的受控访问。可以严格控制客户怎样以及何时访问它。
缺点
- 单例类的职责过重,违背了“单一职责原则”。单例类把实例的创建和本身的业务逻辑融合到一起
- 单例类没有抽象层,扩展困难
应用场景
- 系统只需要一个实例对象,如提供一个唯一的序列号生成器
- 客户调用类的单个实例只允许使用一个公共访问点,除了公共访问点不能通过其它途径访问该实例
- 线程池、缓存