本篇博客主要说明了一下单例模式的六种写法
谈谈单例模式
单例模式,顾名思义就是创建一个唯一的实例。因为有些场景,我们只需要也只能创建一个实例,举个例子:windows是我们所熟悉的系统,我们会发现,当你打开控制面板的时候,无论你打开多少次,都只会有一个控制面板出现,这也是单例模式的一种应用。在java中,我们应用比较广泛的单例模式就有线程池和缓存。
单例模式的六种写法
饿汉模式
1 | public class SingletonTest { |
饿汉模式,用名字来说就是:应用程序很饥饿,急切的需要这个对象来“喂饱”自己。饿汉模式特点是在类加载的时候就初始化了示例对象,它的优点是高效、安全,但缺点就是,如果这是一个使用场景很少,但耗内存的对象,会造成内存的浪费(这种情况就是应用并不“饥饿”,只有在想“吃”的时候才要它)。
当然,这是大家公认的解释,但是稍微了解点设计模式的都知道单一职责原则,也就是一个类只负责一种功能,一般情况下,我们需要使用的单例都只依赖于他的getInstance(),也就是说,我们需要加载单例类的时候就是我们需要这个类的实例的时候,也就不存在内存消耗的问题了。在大多数应用场景里面,我们使用饿汉模式就已经能够满足我们的工作需求了。除非这个类比较特殊,还对外提供了一些static的方法,这种情况饿汉模式就不适用了
懒汉模式
1 | public class SingletonTest { |
懒汉模式,用名字来说就是:我很懒,只有需要我的时候再来实例化我。懒汉模式的特点就是能够实现懒加载(lazy loading),即只在需要的时候才创建实例。但是他的缺点也十分明显,因为要实现多线程下的安全性,所以在方法中加锁导致了性能大大降低了,因为除了第一次初始化需要锁保证安全,其他的时候并不需要加锁。所以我们一般是不会使用懒汉模式的。但是,懒汉模式给我们传递了一种思路,就是懒加载的思路,这个在我们项目的使用和设计的时候,都应该需要有的一个思路。
双重检查锁模式(double-check)
1 | public class SingletonTest { |
双重检查锁是懒汉模式的一种升级版,前面我们说过,单例模式需要保证的就是在初始化实例时候的线程安全问题。所以双重检查锁优化了懒汉模式,将锁的粒度细化,只在创建单例的时候进行加锁。这样,不仅保障了多线程下的安全问题,也保证了效率。
双重检查锁的确是一个比较优秀的单例应用模式,但是双重检查锁也是问题最多的一种模式。因为JVM虚拟机在为了优化执行的速度,在保障happen-before的原则下会产生指令重排序的情况,这样就会出现部分初始化的问题,导致在调用方法的时候抛出初始化异常,解决的方案就是在变量前面加上volatile变量。但实际上,volatile的语义在不同的虚拟机上也不一样,在一些古老的虚拟机上,volatile也并不能完全禁止指定重排序,当然,可能现在也没人用,了解一下即可。(PS:笔者并不推荐使用这种写法,因为有比它更好更便捷的方法)
枚举(天然的单例对象)
1 | /** |
枚举其实没什么好说的,枚举是java的一个语法糖,实际上是一个继承了enum的final类。但是枚举是一个天然的单例模式,用法也非常简单,安全性也是最高的,因为其他方法虽然保证了应用程序方面的安全性,但是面对暴力反射构造创建对象还是没有办法的。当然,也可以通过构造器里抛异常来解决暴力反射,具体可以看看StandardCharsets这个类,他就是通过在构造器里抛异常来阻止暴力反射的。枚举的缺点也很明显的,因为他没法实现懒加载。
静态内部类模式
1 | public class SingletonTest { |
静态内部类就是我所见过的最好的一种单例模式的方法。只要构建一个静态内部类,并在内部类中构造单例对象。这样既保证了多线程情况下的安全性,同时也实现了懒加载。笔者极力推荐大家使用这种方法。
CAS法
1 | public class SingletonTest { |
CAS没什么可说的,实际上就是利用CAS的原子性来保障只有一个对象被成功构建。这种写法也实现了懒加载。当然,这是我在学习的时候看到CAS的原子性想到能不能用CAS来构造单例模式呢,于是自己试了一下写出来,然后google一下发现还真有这种写法。但是其实这种写法的缺陷也是很明显的,那就是虽然能保证能拿到对象是单例的,但并不能保证只实例化一个对象,如果这是一个比较“笨重”的对象,这样就会加重虚拟机内存的负担。
结语
其实,单例模式应该算是设计模式中比较简单的一个模式,但是其中包含的知识点其实并不少,比如说懒加载涉及到了类加载机制,双重检查锁那部分则涉及到了JVM的happen-before和指令重排序的知识点。虽然单例模式无脑使用静态内部类也没什么问题,但是我们学习的目的不是为了怎么方便的写一个单例模式,而是从中学习到设计的思路,为什么有饿汉模式、懒汉模式和双重检查锁等众多的单例模式。这些模式的出现就是前人在系统优化过程中不断研究中创造的,我们真正需要学习的是发散性的思维和他们的创新能力。