设计模式系列(三)---单例模式
单例模式
目录
使用场景
- 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
- 数据库连接池和线程池的设计
- 对于频繁使用或者需要较多的资源的对象,使用单例模式可以节省创建对象的时间,并且可以减少内存的占用
方案一
代码实现
public class Singleton {
private static Singleton instance;
private Singleton()
{}
public static Singleton getInstance()
{
if(instance==null)
{
instance=new Singleton();
}
return instance;
}
}
分析
- 构造方法用了private,说明不能通过new创建对象
- 当多线程执行getInstance的时候,比如同时执行到if语句,==成立,此时两个线程都创建了两个实例
方案二
代码实现
public class Singleton {
private static Singleton instance;
private Singleton()
{}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析
- 对比上面唯一的改变就是getInstance方法被synchronized修饰,这样的同一时间只能有一个线程创建实例,这样就解决了上面的问题
- 但是这种实现无论instance是否为null都会进行加锁。但是如果我们已经创建instance不为null的话,并没有必要进行加锁
方案三
代码实现
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
分析
- 对比上一种,貌似解决了上面说的情况,就是只有instance为null的时候才进行加锁。
- 但是实际上这种方式还是有问题,因为这存在重排序的问题
重排序
- 指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
- As-if-serial语义的意思是,所有的动作都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
int a=1;
int b=2;
int c=a+b;
-
第一句和第二句的前后顺序,并不会造成影响,所以在优化时可以根据情况重排序
-
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果。但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
-
再回头看一下instance=new Singleton(),这段代码是通过三步完成的,分配内存,初始化成员变量,将instance对象指向分配的内存空间。那么如果出现重排序,先将对象指向内存再来初始化成员变量。如果线程A此时执行完指向内存这一步(还没有进行初始化),然后线程B执行getInstance,因为线程A已经将nstance指向分配的内存空间,那么线程B直接返回这个instance,但是这个instance并没有初始化成员变量,如果我们这个使用这个没初始化的对象,这个时候就会出现错误
volatile
- volatile有两个重要的作用,第一个是解决可见先的问题,写操作完成后,必须立刻刷新到主内存,读操作的时候,必须立即马上从主内存刷新到工作内存
- 第二个就是禁止指令重排序优化
方案四
代码实现
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
分析
- 将Singleton用volatile修饰,解决了上面发生的由于指令重排出现的问题
方案五
代码实现
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
分析
- 了解类加载机制的会知道,当静态常量成员在准备阶段就会进行初始化
- 那么这就会出现一个问题,即使我们没有调用getInstance方法,都会创建一个实例
方案六
代码实现
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
分析
- 这里静态内部类并没有被初始化,那么就解决了上面的问题,只有调用的时候才会创造实例
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。