单例模式的简单介绍
单例模式很简单,就是在整个系统运行过程中,保证类的实例只有一个。
单例模式的类图如下:
单例模式的具体实现思路
- 将构造函数私有化,防止从外部通过new实例化对象
- 在类内部生成一个实例化对象
- 通过public类型方法返回这个唯一的实例化对象
单例模式的具体实现方案
饿汉模式,通过直接给static、final修饰的内部属性赋值,然后通过静态方法返回
该种方式可以说是一种“最优解”,它的代码最为简单、直观,而且能够保证线程安全,虽然说懒汉式能够优化饿汉式,让其在调用时才创建对象,而不是类装载时就创建对象,但是懒汉式就会带来线程不安全的问题,让人非常恼火,虽说有解决方案,但是不如饿汉式更加简单直观。懒汉式最要命的问题就是,一但通过类加载器加载,就会分配空间,如果没使用就会造成空间浪费,但是换个角度想象一下,我不使用它,我干嘛去将它装载到类加载器中。
它可能是工作中用到最多的单例写法。
public class Singleton { // 用static、final修饰 private static final Singleton INSTANCE = new Singleton(); // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getInstance() { return INSTANCE; } }
还有一些写法是不直接赋值,通过静态代码块赋值,效果是一样的。
最安全解决方案,枚举单例
枚举单例可能用到的会很少,但是他是单例最安全的一种实现。它不仅能够保证线程安全性,还能防止反射破坏单例,防止序列化破坏单例,但是他也是饿汉式的。
public enum Singleton { // 单例对象 INSTANCE; // 其它方法 }
最完美解决方案,静态内部类 最推荐写法
通过静态内部类创建单例模式,不但是懒汉式的,而且它通过静态初始化类也是能够避免线程安全问题的。
public class Singleton { // 静态内部类 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
懒汉模式
懒汉模式虽然写法简单直观,但是带来严重的线程安全问题。
public class Singleton { // 属性 private static Singleton instance; // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
DCL双重检测锁模式
复杂,不易理解,但是能够解决懒汉式,线程不安全的问题。
public class Singleton { // volatile修饰,保证原子性 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; } }
双重检测锁模式,还可以借助一个开关,未创建对象时打开,创建完成关闭,这样就能在一定程度上防止反射破坏单例,因为反射虽然可以忽略private,但是它并不清楚开关具体是如何实现的,字段名,开关值都不知道。
可以这么说,双重检测锁虽然是单例中相对完美的实现,但是它的代码不易理解,并不是一种比较好的写法。
单例模式的优缺点
优点:
- 在单例模式中,活动的实例只有一个,对单例类的所有实例化得到的都是相同的一个实例。这样就可以防止其它对象对单例类的实例化,确保所有的对象都访问同一个实例
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 避免对共享资源的多重占用。
缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单利模式没有抽象层,因此单例类的扩展很困难。
- 单例类的职责过重,在一定程度上违背了单一职责原则。
单例模式的适用场景
适用场景:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 不怎么发生变化,但又经常被用到。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
举例:
- 各种Manager,一般管理器都只有一个实例,如Windows的任务管理器
- 计数器
- 应用的配置,系统的配置
- 共享的资源
- 各种池化技术,数据库连接池,线程池...
- ...
单例模式总结
通过对单例的学习,我最大的收获是,我们在使用设计模式是不要一味去追求最优的实现,能解决我们项目中的问题,并且更容易被其他人所接受,才是最好的实现。单例模式比较完备的实现方案就是我文中提到的这五种,最推荐大家使用的就是静态内部类的方式,次推荐的就是枚举和饿汉式,我认为一般的项目饿汉式带来的内存损耗微乎其微,但是如果出现并发,致使单例不再是单例,这样是我所不能接受的。双重检测锁模式,虽然也相对完美,但是写法复杂不易理解,如果忘记volatile修饰,也是无法保证线程安全问题,所以我也不是很推荐双重检测这种写法。