Singleton-单例模式
1.单例简介
在你确信你的类对应的对象或者实例只需要一个的情况下,单例模式就比较适用。如数据库线程池、读配置文件等都是原型的示例。
2.单例实现模式
Java中static的特性:
-
static变量在类装载的时候进行初始化
-
多个实例的static变量会共享同一块内存区域
利用static的特性,我们就能够控制值创造一个实例。
2.1 饿汉式(预先加载)
饿汉式最常见的实现方式:
1
2
3
4
5
6
7
8
9
10
11
|
public class Singleton
{
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance()
{
return instance;
}
}
|
还有另外一种方式,区别不大:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class Singleton
{
private Singleton instance = null ;
static
{
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance()
{
return this .instance;
}
}
|
优点:
1.线程安全
2.类加载的同时已经创建好一个静态实例,调用时反应快
缺点:
资源利用效率不高,可能getInstance永远不会执行到
饿汉式的方式在一些场景中将无法使用,如实例的创建依赖于参数。
2.2 懒汉式(延迟加载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Singleton
{
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance()
{
if (instance == null )
{
instance = new Singleton();
}
return instance;
}
}
|
我们一般写懒汉式都是以上的方式,但是在多线程环境下,是不安全的,很容易导致在内存中创建两个实例,李逵李鬼,问题就来了。
于是,我们就很容易的想到为getInstance()方法加上synchronized标识符,这样就可以保证不会出现线程问题了,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Singleton
{
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance()
{
if (instance == null )
{
instance = new Singleton();
}
return instance;
}
}
|
除了第一次调用的时候执行实例的生成之外,以后每次都是直接返回实例。那么后面每次调用getInstance()方法都会进行同步准备,会有很大的消耗,性能上很不划算。于是我们把代码改成如下的方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class Singleton
{
private static Singleton instance;
private Singleton()
{
}
public static Singleton getInstance()
{
synchronized (Singleton. class )
{
if (instance == null )
{
instance = new Singleton();
}
}
return instance;
}
}
|
基本上按照以上方式把synchronized移到getInstance()方法中没有任何意义,每次调用还是需要进行同步。我们只是希望在第一次创建Singleton时进行同步,因此有了以下的方式,双重锁定检查(DCL).
2.3 双重锁定检查(DCL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class Singleton
{
private static Singleton instance;
private Singleton()
{
}
public static Singleton getInstance()
{
if (instance != null )
{
synchronized (Singleton. class )
{
if (instance == null )
{
instance = new Singleton();
}
}
}
return instance;
}
}
|
看上去这样已经达到了我们的要求,但是这样就没有问题了么?
假设线程执行到了
1
|
instance = new Singleton();
|
这一句,这里看上去是一句话,但是实际上并不是一个原子操作(原子操作的含义是要么没有执行,要么执行完)。在高级语言中非原子操作很多,通过JVM的后台,这一句话做了大概三件事情:
-
给instance分配内存
-
初始化Singleton构造器
-
将instance实例指向分配的内存空间(instance非null)
但是,由于Java编译器是乱序执行的,以及JDK5之前的内存模型中缓存、寄存器到主内存回写的顺序,上面2,3的顺序无法保证。如果线程1执行时先执行3后执行2,正在3执行完,2未执行之际,线程2调用获得instance去使用,报错。如果通过调试去解决故障,估摸着一周都找不出错误所在。
此方法在一些语言中是可行的,如C语言。在JDK5之后,已经注意到此问题,调成了内存模型,并具体化了volatile,因此在JDK5之后版本,只需要将instance标识为volatile,就可以保证执行的顺序,具体如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class Singleton
{
private volatile static Singleton instance;
private Singleton()
{
}
public static Singleton getInstance()
{
if (instance != null )
{
synchronized (Singleton. class )
{
if (instance == null )
{
instance = new Singleton();
}
}
}
return instance;
}
}
|
volatile或多或少会影响一些性能,而且在JDK1.42之前无法使用,看一下单例其他实现方式。
2.4 静态内部类实现单例
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Singleton
{
private static class SingletonHolder
{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance()
{
return SingletonHolder.instance;
}
}
|
这种写法仍然使用了JVM本身的机制保证了线程安全的问题,由于SingletonHolder是私有的,除了getInstance()方法之外没有其他方法访问。这种也是懒汉式的。同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖JDK的版本。
2.5 总结和扩展
其他单例的写法还有很多,如JDK5之后通过枚举的方式、使用本地线程(ThreadLocal)来处理并发以及保证一个线程内一个实例的实现、GOF原始例子中用注册方式、使用指定类加载器去应对多个加载器的实现等等,我们在做设计时,需要考虑可能的扩展和变化,也要避免无谓的提升设计、实现复杂度等。设计不足和过度设计都是危害,找到最合适的方式才是最好的。
目前单例模式介绍完,最后介绍一下其他途径屏蔽构造单例实例的方法:
-
new 单例对象
通过加入private或者protected的构造方法,就无法直接通过new的方式构造
-
反射构造单例对象
反射时可以使用setAccessible方法来突破private的限制,这就需要在 ReflectPermission("suppressAccessChecks") 权限下使用安全管理器(SecurityManager)的checkPermission方法来限制这种突破。一般来说,不会真的去做这些事情,都是通过应用服务器进行后台配置实现。
-
序列化构造单例对象(很少出现)
如果单例需要实现Serializable接口,那么应当实现readResolve()方法来保证反序列化的时候得到原来的对象
基于以上,使用静态内部类的单例模式实现可能如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import java.io.Serializable;
public class Singleton implements Serializable
{
private static final long serialVersionUID = 2382773738822334655L;
private static class SingletonHolder
{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance()
{
return SingletonHolder.instance;
}
private Singleton()
{
}
private Object readResolve()
{
return getInstance();
}
}
|
到此为止,介绍了单例模式的JAVA实现,并介绍了各自的优缺点。单例模式是一种即简单又复杂的模式。
2.6 参考
请不吝赐教。
@anthor ClumsyBirdZ
分享到:
相关推荐
设计模式系列之01-单例模式(Singleton模式),很好的资源,理论实践结合讲述,逐步更新
Java面向对象(高级)-- 单例(Singleton)设计模式
Singleton pattern单例模式应用
NULL 博文链接:https://linxingliang.iteye.com/blog/1217811
java Singleton单例模式 java Singleton单例模式
在Java应用中,单例对象能保证在一个...3、有些像交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了,只有使用单例模式,才能保证核心交易服务器独立控制整个流程。 CSDN代码的详细解释。
此示例展示了Qml 的单例模式(类似全局对象,只生成一次实例,可全局使用) surfsky.cnblogs.com
java设计模式,单例模式的不同实现方式
单例模式 Singleton 单例模式线程安全问题和拓展
singleton单例模式1
Android Singleton单例模式运用详解
设计模式之单例模式。Java代码实现。五种实现方式
Objective-c 单例模式的完整书写方式,用了很久的单例模式了,却发现singleton的用法也是蛮有讲究的。不过一般情况下,即使不书写完整的单例,也不会出现什么意外情况。。
设计模式C++学习之单例模式(Singleton)
c++ singleton单例模式
php /** * 单例模式 * * 保证一个类仅有一个实例,并提供一个访问它的全局访问点 * */ class Singleton { static private $_instance = null; private function __construct() { } static public function ...
本文档,是利用C++来实现设计模式中的,单例模式,里面有内容说明和相关实例代码介绍
设计模式总结-模板设计模式,单例模式(singleTon)
- 单例模式(Singleton) - 建造者模式(Builder) - 原型模式(Prototype) - 代理模式(Proxy) - 适配器模式(Adapter) - 装饰器模式(Decorator) - 桥接模式(Bridge) - 组合模式(Composite) - 外观模式(Facade) ...
React单例钩子 使用钩子管理 React 应用程序的全局状态。 安装 要在 React 应用程序中使用 React Singleton Hook,请将其安装为依赖项: # If you use npm: npm install react-singleton-hook # Or if you use ...