目录
(1)eagerly initialized singleton
(2)lazily initialized singleton
(5)initialize on demand holder idiom
单例模式,在配置文件的时候用的比较多,各处的配置都保持统一性。
一、什么是单例模式?
layout | title | folder | permalink | categories | tags | |||
---|---|---|---|---|---|---|---|---|
pattern | Singleton | singleton | /patterns/singleton/ | Creational |
|
布局 | 标题 | 文件夹 | 永久链接 | 分类 | tags | |||
---|---|---|---|---|---|---|---|---|
模式 | 单例 | singleton | /patterns/singleton/ | 构建累 |
|
在设计模式中,《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书)由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)"。GoF就是Gang of Four
Intent
含义
Ensure a class only has one instance, and provide a global point of access to it.
确保一个类只有一个实例,并且提供一个方法来获取它。
Explanation
解释
Real world example
真实案例
There can only be one ivory tower where the wizards study their magic. The same enchanted ivory tower is always used by the wizards. Ivory tower here is singleton.
这里只能有一个巫师学习魔法的象牙塔。同样加强的象牙塔,被巫师经常使用。象牙塔在这就是单例。
In plain words
简单地说:
Ensures that only one object of a particular class is ever created.
确保特定类只有一个对象被创建出来。
Wikipedia says
维基百科(Wikipedia)说:
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
在软件工程,单例模式是一种限制类仅实例化一个对象的软件设计模式。在仅需一个对象来调整系统反应时,这个模式是很有用的。
Programmatic Example
程序案例
Joshua Bloch, Effective Java 2nd Edition p.18
Effective Java 2nd (嘿嘿,这本书我也有)
A single-element enum type is the best way to implement a singleton
一个但元素枚举类型,是单例模式的最佳实现
public enum EnumIvoryTower {
INSTANCE;
}
Then in order to use
然后使用单例(可以啊,以前我还没试过这种方式,学到了!):
EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE;
assertEquals(enumIvoryTower1, enumIvoryTower2); // true
Applicability
应用
Use the Singleton pattern when
当如下情况时,用到单例模式:
- there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point
- when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code
- 一个类必须恰好有一个实例,并且它必须提供给用户一个熟知的调用入口‘
- ’当这个唯一的对象需要被子类继承时,而且用户需要能够使用一个被继承的实例而不用修改他们的代码。
Typical Use Case
典型使用案例
- the logging class
- managing a connection to a database
- file manager
- 日志类
- 在数据库中,管理连接
- 文件管理
Real world examples
真实案例
Consequences
经验结论
- Violates Single Responsibility Principle (SRP) by controlling their own creation and lifecycle.
- Encourages using a global shared instance which prevents an object and resources used by this object from being deallocated.
- Creates tightly coupled code. The clients of the Singleton become difficult to test.
- Makes it almost impossible to subclass a Singleton.
- 控制他们的构建和生命周期,违反单一功能原则(SRP)
- 鼓励使用一个全局实例,它能防止一个对象和资源被这个对象使用而解除分配
- 写紧耦合代码。使用单例的用户会难以测试。
- 让继承一个单例变得几乎不可能
Credits
二、代码分析与讲解
编程最好玩的地方就在于可以立马看到结果,还能自己控制,有结果的工作是很容易让人开心的!
让我们让看一看这一段代码:
1,单例模式说明
/**
* Singleton pattern ensures that the class can have only one existing instance per Java classloader
* instance and provides global access to it.
单例模式保证了一个类在每个java类加载实例汇中只能有一个存在的实例,而且提供全局的调用方法。
* <p>
* One of the risks of this pattern is that bugs resulting from setting a singleton up in a
* distributed environment can be tricky to debug, since it will work fine if you debug with a
* single classloader. Additionally, these problems can crop up a while after the implementation of
* a singleton, since they may start out synchronous and only become async with time, so you it may
* not be clear why you are seeing certain changes in behaviour.
这个模式的一个风险是在分布式环境下设立单例模式时,debug非常棘手,因为在只有一个类加载器时它是正常的!(记得这一点,说不定就遇上了)。而且,这些问题在继承了一个单例累后会突然出现,因为他们可能同步出发,然后只是偶尔异步。所以你也许对于这些变化不是非常明了。
* <p>
* There are many ways to implement the Singleton. The first one is the eagerly initialized instance
* in {@link IvoryTower}. Eager initialization implies that the implementation is thread safe. If
* you can afford giving up control of the instantiation moment, then this implementation will suit
* you fine.
这里有很多办法来实现单例。第一种办法是急切的加载实例(感觉是饥汉式)。急切的初始化暗示了这种实现方式是线程安全的。如果你能够承受得了实例化时间的耗费,这种实现方式会适合你。
* <p>
* The other option to implement eagerly initialized Singleton is enum based Singleton. The example
* is found in {@link EnumIvoryTower}. At first glance the code looks short and simple. However, you
* should be aware of the downsides including committing to implementation strategy, extending the
* enum class, serializability and restrictions to coding. These are extensively discussed in Stack
* Overflow:
* http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing
* -a-singleton-with-javas-enum
另一种实现先初始化单例的方式是枚举类式的单例。在EnumIvoryTower可以看到。第一眼看过去,代码又短又简单。然而,你应该要清楚,缺点包括提交的实现策略,继承枚举类,序列化和编码限制。这些扩展行阅读可以在Stack Overflow看到:
http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing-a-singleton-with-javas-enum
* <p>
* {@link ThreadSafeLazyLoadedIvoryTower} is a Singleton implementation that is initialized on
* demand. The downside is that it is very slow to access since the whole access method is
* synchronized.
ThreadSafeLazyLoadedIvoryTower 是一种在需要时才初始化的一种单例实现方式(饱汗式)。缺点在于它的调用是非常慢的,因为所有的获取方法是同步的。
* <p>
* Another Singleton implementation that is initialized on demand is found in
* {@link ThreadSafeDoubleCheckLocking}. It is somewhat faster than
* {@link ThreadSafeLazyLoadedIvoryTower} since it doesn't synchronize the whole access method but
* only the method internals on specific conditions.
另一种单例的实现方式,是按需初始化,在ThreadSafeDoubleCheckLocking可以看到。它是一种比ThreadSafeLazyLoadedIvoryTower更快的实现方式,因为在获取对象方法时它不是同步的,只有方法里的特定条件下才是同步的。
* <p>
* Yet another way to implement thread safe lazily initialized Singleton can be found in
* {@link InitializingOnDemandHolderIdiom}. However, this implementation requires at least Java 8
* API level to work.
*/
还有另一种实现线程安全的懒加载单例的方式能在InitializingOnDemandHolderIdiom看到。然而,这种实现方式要求至少是Java 8 API才能实现。
2,一行一行读代码
单例模式的核心在于创建对象的时候做出种种防御措施,确保一个类只有一个对象。原来一直记得饥汉式和饱汉的实现方式,但这里使用了五种实现方式,让我们来逐一看看。代码在singleton
(1)eagerly initialized singleton
// eagerly initialized singleton
这个大概就是饥汉式咯,一开始不管三七二十一,先把对象new出来。
仔细看:
IvoryTower ivoryTower1 = IvoryTower.getInstance();
也就是说获取对象不是new而是用getInstance,这个方式在工厂中也有用到
public static IvoryTower getInstance() {
return INSTANCE;
}
那么INSTANCE从哪里来?
private static final IvoryTower INSTANCE = new IvoryTower();
也就是说在加载类信息时就会初始化这个对象,后续都不会再初始化了。
所以当打印出这两个对象时,对象地址是一样的
11:43:43.150 [main] INFO com.iluwatar.singleton.App - ivoryTower1=com.iluwatar.singleton.IvoryTower@1936f0f5
11:43:52.538 [main] INFO com.iluwatar.singleton.App - ivoryTower2=com.iluwatar.singleton.IvoryTower@1936f0f5
这种方式是最简单的,也很好理解,但是在多线程、分布式时操作起来会有问题,比如有两个类加载器,那是不是就有两个单例了?
(2)lazily initialized singleton
lazily initialized singleton 也可以叫做饱汉式单例吧。老是说,什么鬼饥汉、饱汉,完全就是图个形象,单对于理解没什么用。lazy明显好理解好吗,lazy一般就可以理解成延迟加载了。
ThreadSafeLazyLoadedIvoryTower threadSafeIvoryTower1 =
ThreadSafeLazyLoadedIvoryTower.getInstance();
它获取单例的函数:
public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {
if (instance == null) {
instance = new ThreadSafeLazyLoadedIvoryTower();
}
return instance;
}
再来看下它的构造函数:
private ThreadSafeLazyLoadedIvoryTower() {
// protect against instantiation via reflection
if (instance == null) {
instance = this;
} else {
throw new IllegalStateException("Already initialized.");
}
}
第一单例的构造函数,都是private,不允许直接调用,避免了new对象带来的多例;第二,同步机制避免了同时创建对象造成不一致问题,但是方法同步限制了效率。比较巧妙的是在构造方法中,还加了一步对instance的限制,这里的注释说的是避免反射造成的影响。第四,方法和变量都是static,因为它的调用是通过类而不是对象。
(3)enum singleton
枚举型的单例,以前我是没见过,也算涨见识了。先来看看调用:
EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
这种方式是在effect java中的一种实现方式(顿时想把这本书翻一遍,很灵性有木有),它的实现方式很简单,只用在enum中定义:
INSTANCE;
这样就可以了,而且它是线程安全的!为啥是线程安全?感觉得要看看enum,也看过一些代码里还喜欢用enum。
(4)double checked locking
这也是effective java的一个案例,这种方式能提升本地变量25%的效率,核心代码是这一段:
public static ThreadSafeDoubleCheckLocking getInstance() {
ThreadSafeDoubleCheckLocking result = instance;
if (result == null) {
synchronized (ThreadSafeDoubleCheckLocking.class) {
result = instance;
if (result == null) {
instance = result = new ThreadSafeDoubleCheckLocking();
}
}
}
return result;
}
它只在空的时候才进行线程同步,但是这里很有意思,判断了两次result == null,
第一次判断 result 是否为null,是判断单例是否被初始化,它如果没有被初始化,我们也不能简单地进行初始化,因为可能有其他线程进来了,所以就要加同步。synchronized (ThreadSafeDoubleCheckLocking.class)之后,再用本地的instance变量检查单例是否被初始化了。巧妙地在于第二次检查,已经通过了第一次的result == null 之后,可能卡在synchronized,所以需要进到同步代码后再检查一遍。
double check的关键在于第二次检查,防止被阻塞的代码重复初始化。这段代码对于非空情况,只用检查一次,效率可以大大提升。
(5)initialize on demand holder idiom
这种方式,按需初始化,是一种安全的懒加载模式。这种技术,尽可能得“懒”,并可以在所有的java版本中应用,它利用了类初始化的语法检查,因此在各种java编译器和虚拟机上都可以运行。
这么神奇?!因为它用了内部类!仔细看:
public static InitializingOnDemandHolderIdiom getInstance() {
return HelperHolder.INSTANCE;
}
获取实例的方式没什么两样,这个HelperHolder是什么东东?它是 InitializingOnDemandHolderIdiom的一个内部类
private static class HelperHolder {
private static final InitializingOnDemandHolderIdiom INSTANCE =
new InitializingOnDemandHolderIdiom();
}
擦,这是我见过用内部类写的第一段神奇的代码,还可以这么用!为什么可以呢?因为内部类被引用不会早于getInstance方法被调用(因此,不会被类装载器更早加载)。因此这种解决方案是线程安全的,却不用特别的语法构造(像volatile和synchronized)。
为什么这样实现就是单例的?
因为这个类的实例化是靠静态内部类的静态常量实例化的。
INSTANCE 是常量,因此只能赋值一次;它还是静态的,因此随着内部类一起加载。
贼全五种单例构造方法,顿时感觉到学习语言的魅力,一个简单的设计模式有如此多的实现方式,佩服。我个人比较喜欢枚举和initialize on demand holder idiom 实现方式。
三、单例模式的实现方式
1,懒汉模式
线程不安全,延迟初始化,严格意义上不是不是单例模式,if (instance == null)这一句在多线程下不能保证单例。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
还有一种将getInstance改为,同步方式,但效率低,但它是能实现单例的:
public static synchronized Singleton getInstance()
懒汉式还有一种尝试,把锁加在方法里面:
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton .class) {
instance = new Singleton();
}
}
return instance;
}
这种方法实际上不能实现单例,比如有多个线程走到了走到了synchronized这一行,他们会依次初始化。乃至这种方式下采用双重锁方式,照样不能实现单例,因为初始化有3个步骤:分配内存空间、引用指向空间、初始化对象,可能存在没初始化对象的情况下另一个线程进来(加了volatile 是可以的,就是后面讲的双重检查是ok的):
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton .class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
详解:
sInstance =new Singleton();这句话创建了一个对象,他可以分解成为如下3行代码:
1 2 3 | memory = allocate(); // 1.分配对象的内存空间 ctorInstance(memory); // 2.初始化对象 sInstance = memory; // 3.设置sInstance指向刚分配的内存地址 |
上述伪代码中的2和3之间可能会发生重排序,重排序后的执行顺序如下
1 2 3 | memory = allocate(); // 1.分配对象的内存空间 sInstance = memory; // 2.设置sInstance指向刚分配的内存地址,此时对象还没有被初始化 ctorInstance(memory); // 3.初始化对象 |
因为这种重排序并不影响Java规范中的规范:intra-thread sematics允许那些在单线程内不会改变单线程程序执行结果的重排序。
但是多线程并发时可能会出现以下情况
线程B访问到的是一个还未初始化的对象。
2,饿汉模式(静态常量模式,也有用静态代码块实现)
线程安全,比较常用,但容易产生垃圾,因为一开始就初始化。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
3,双重锁模式
线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。将对象声明为volatitle后,重排序在多线程环境中将会被禁止
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰signleton实例变量有效,解决该问题。
4,静态内部类单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
5,枚举单例模式
public enum Singleton {
INSTANCE;
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
枚举类隐藏了私有的构造器。
枚举类的域 是相应类型的一个实例对象
那么枚举类型日常用例是这样子的:
public enum Singleton {
INSTANCE
//doSomething 该实例支持的行为
//可以省略此方法,通过Singleton.INSTANCE进行操作
public static Singleton get Instance() {
return Singleton.INSTANCE;
}
}
枚举单例模式在《Effective Java》中推荐的单例模式之一。但枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。
在以上所有的单例模式中,推荐静态内部类单例模式。主要是非常直观,即保证线程安全又保证唯一性。
众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化。当实例被写入到文件到反序列化成实例时,我们需要重写readResolve方法,以让实例唯一。
private Object readResolve() throws ObjectStreamException{
return singleton;
}
四、典型应用
1,java.lang.Runtime
Runtime类时典型的饿汉式单例模式。
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {
}
......
}
2,spring依赖注入生成单例
五、使用场景
需要频繁地进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。
参考:
1,Java并发变成艺术
3,github design-pattern