23种设计模式【Java】解析-单例模式

单例模式

确保一个类只有一个实例,并提供该实例的全局访问点,。

类图

使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。

私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

6种实现方式

实现方式总结

1.懒汉式-线程不安全

私有静态变量 instance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 instance,节约资源。

但多线程环境下是不安全的,如果多线程能够同时进入 if (instance == null) ,且 instance 为 null,那么会有多个线程执行 instance = new Singleton(); 语句,导致多次实例化 instance。

public class Singleton {
  
  private Singleton instance;
  
  private Singleton(){
    
  }
  
  public static Singleton getInstance() {
    if(instance == null){
      instance = new Singleton();
    }
    return instance;
  }
}
2.饿汉式-线程安全

线程不安全问题主要于 instance 被多次实例化,采取直接实例化 uniqueInstance 的方式线程安全了。

但直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。

public class Singleton {
    
    private static Singleton instance = new Singleton();
    
    private Singleton() {
    }
    //对外提供获取实例的静态方法
    public static Singleton getInstance() {
        return instance;
    }
}
3.懒汉式-线程安全

只要对 getInstance() 方法加锁,在一个时间点只有一个线程能够进入,就避免了多次实例化的问题。

但当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,性能上有一定的损耗。

public class Singleton {
    
    private static Singleton instance = new Singleton();
    
    private Singleton() {
    }
  
  	public static synchronized Singleton getInstance() {
    	if (instance == null) {
      	  instance = new Singleton();
    	}
    	return instance;
		}
}
4.双重校验锁-线程安全

懒汉线程安全方式加锁范围较大,可以只针对实例化部分,且只有 instance 未被实例化时,才加锁。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

双重锁校验是为了避免多线程同时执行第一个 if ,而创建多个实例。

用 volatile 关键字是为了防止指令重排instance = new Singleton(); 这段代码分为三步。

  1. 分配内存空间
  2. 初始化对象
  3. 将 uniqueInstance 指向分配的内存地址

由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1 > 3 > 2,如果是多线程下,可能获得是一个还没有被初始化的实例。

5.静态内部类实现

当 Singleton 类加载时,静态内部类 SingletonHolder 没被加载进内存。当调用 getInstance() 方法触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。

这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。

public class Singleton {

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}
6.枚举实现

最佳实践,实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。

public enum Singleton {
    instance;
  	Singleton() {
    }
}

优缺点

优点
  • 提供了对唯一实例的访问控制。
  • 由于在系统内存中只存在一个对象,因此可以节约资源。
  • 允许可变数目的实例。可基于单例模式进行扩展,使用相似的方法来获得指定个数的对象实例。
缺点
  • 由于没有抽象层,扩展有很困难。
  • 职责过重,在一定程度上违背了“单一职责原则”。既充当了工厂角色,又充当了产品角色。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;比如 JVM 的自动垃圾回收的技术,如果实例化的对象长时间不被利用,会自动销毁并回收资源,下次又将重新实例化,将导致对象状态的丢失。

适用场景

在以下情况下可以使用单例模式:

  • 系统只需要一个实例对象,或需要考虑资源消耗问题。
  • 客户调用类的单个实例只允许使用一个公共访问点。
  • 一个系统中要求一个类只有一个实例时。如果一个类可以有几个实例共存,就需要改进,成为多例模式。
应用
  1. java.lang.Runtime;
  2. GUI 中也有一些(java.awt.Toolkit#getDefaultToolkit() java.awt.Desktop#getDesktop())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值