单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例。

其中涉及到最主要的问题就是在多线程并发时线程安全问题。

单例模式的实现也有一个循序渐进的过程:
1.最基本要求:每次从getInstance()都能返回一个且唯一的一个Singleton对象。
2.稍微高一点的要求:能适应多线程并发访问。
3.再提高一点的要求:提高获得Singleton实例的性能。
4.最后一点要求是:实现懒加载(Lazy Load),在需要的时候才被构造。

——————————————————————————————–

1. 单例模式最基本的实现 = 私有构造函数 + 私有静态属性 + 公有静态Getter方法判断实例化

 public class Singleton {

     private static Singleton uniqueInstance = null;

     private Singleton() {}

     public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

——————————————————————————————–

2. 如果考虑线程并发,最直接的想法就是加线程锁(synchronized)来解决线程同步问题,可以适应多线程并发,但是这里有个很大的性能问题,每一次调用getInstance都会进行同步准备,代价很大,其实我们只有在创建实例的问题上才需要解决线程安全问题。

public class Singleton {

    private static Singleton uniqueInstance = null;

    private Singleton() {}

    public synchronized static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

——————————————————————————————–

3. 为了提高性能,我们只希望在第一次创建Instance实例的时候进行同步,使用双重检测加锁(DCL)机制,判断是第一次实例化后锁定对象,然后再synchronized块中再次判断。

public class Singleton {

    private static Singleton uniqueInstance = null;

    private Singleton() {}

    public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized(Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

DCL的写法来实现单例是很多资料上推荐的写法,实际上是不完全正确的。代码看似没有问题,但是测试中却发现线程大量并发访问时会报NullPointException,调试很久没有结果。

主要原因是因为 uniqueInstance = new Singleton();这句代码并非原子操作,在JVM执行的汇编代码中分为以下几步:
步骤1:给Singleton的实例分配内存空间。
步骤2:初始化Singleton构造器
步骤3:将uniqueInstance指向第一步中分配的内存空间。

步骤2和步骤3在JMM中是无法保证顺序的,所以如果1-3-2的情况发生,将造成RuntimeException:
线程A进入同步块,执行步骤1-3,此时uniqueInstance已经指向了内存空间,但是Singleton还未构造,此时uniqueInstance已经非空,线程B直接return uniqueInstance。

关于添加volatile关键字,jvm虚拟机保证每次uniqueInstance的值都是从主内存中读取,这种方案是可行方案之一。

——————————————————————————————–

4. 重新设计后,我们把初始化实例的事情扔给JVM。因为JVM保证一个类在一个ClassLoader中只会被初始化一次。

public class Singleton {

    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
return uniqueInstance;
}
}

这是传说中的【饿汉式单例】,全局的单例在类装载时第一时间被构建并初始化。
优点:速度快,提前创建。
缺点:某些需要配置参数传入getInstance构建的Singleton无法使用

——————————————————————————————–

5. 我们还是回归第一次被使用时创建的懒加载,同样把初始化实例的事情交给JVM,这次我们使用内部私有类来创建实例。

public class Singleton {

    public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
} private static class SingletonHolder {
static final Singleton INSTANCE = new Singleton();
}
}

这是【懒汉式单例】的一种,SingletonHolder是私有内部类,所以只有getInstance可以访问,读取实例的时候也不会同步,性能上和安全上比较平衡。

——————————————————————————————–

结语:

单例模式的实现方式还有很多,在不同语言不同需求下的实现也有不同的考量和标准,作为一个最简单又最复杂的设计模式,我们应该去尝试每种实现的潜在问题和解决方案,需要更多的理解和尝试。

DP #1 Singleton Pattern线程安全问题的更多相关文章

  1. singleton pattern的推荐实现

    一.单例模式的C#实现: (1)使用double-checked locking的方式: public sealed class Singleton { private static volatile ...

  2. 【设计模式】单例模式 Singleton Pattern

    通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance)  的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...

  3. Java 设计模式(三)-单例模式(Singleton Pattern)

    1     概念定义 1.1   定义 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 1.2   类型 创建类模式 1.3   难点 1)多个虚拟机 当系统中的单例类被拷贝运行在多 ...

  4. Java设计模式—单例设计模式(Singleton Pattern)全然解析

    转载请注明出处:http://blog.csdn.net/dmk877/article/details/50311791 相信大家都知道设计模式,听的最多的也应该是单例设计模式,这种模式也是在开发中用 ...

  5. 设计模式01 创建型模式 - 单例模式(Singleton Pattern)

    参考 [1] 设计模式之:创建型设计模式(6种) | 博客园 [2] 单例模式的八种写法比较 | 博客园 单例模式(Singleton  Pattern) 确保一个类有且仅有一个实例,并且为客户提供一 ...

  6. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

  7. Singleton Pattern -- 不一样的单例模式

    Singleton Pattern -- 单例模式 单例模式是用来创建一个只能又一个实例的对象. 单例模式类图如下. 单例模式有两大好处: (1)对于频繁使用的对象,可以省略创建对象所话费的时间,这对 ...

  8. Net设计模式实例之单例模式( Singleton Pattern)

    一.单例模式简介(Brief Introduction) 单例模式(Singleton Pattern),保证一个类只有一个实例,并提供一个访问它的全局访问点.单例模式因为Singleton封装它的唯 ...

  9. 浅谈设计模式--单例模式(Singleton Pattern)

    题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...

随机推荐

  1. vc++上的MFC的对象序列化和反序列化

      注意点: 1. 必须类型序列化声明    DECLARE_SERIAL( Person )  2. 必须写出实现宏 IMPLEMENT_SERIAL(Person, CObject, VERSIO ...

  2. shell 死循环

      例1: 运行脚本后会自动加载firefox浏览器,并打开指定网页.如果用户关闭firefox,脚本会再次自动重新打开firefox.     如需结束循环,中止t2.sh进程即可.注意不要同时执行 ...

  3. [转] stat命令输出结果中, Access,Modify,Change的含义

    先建立一个空白文件a.txt 1 [emduser@emd tmp]$ touch a.txt 2   3 [emduser@emd tmp]$ ls -al a.txt 4   5 -rw-rw-r ...

  4. android 接听和挂断实现方式

    参考:android 来电接听和挂断 支持目前所有版本 注意:android2.3版本及以上不支持下面的自动接听方法. (会抛异常:java.lang.SecurityException: Neith ...

  5. wxpython下的桥梁信息管理系统

    github地址:https://github.com/billiepander/BIMS 第一版: 现在实现了登陆,与部门级别用户录入桥梁检测信息后保存为excel(后期要用数据库存一些关键信息,为 ...

  6. 熟悉java堆内存和栈内存和mysql的insert语句中含有id的处理

    java的堆内存和栈内存有什么区别呢? 如果mysql数据库表的id是递增的,如果没有插入id,则id自增,如果插入id,则插入什么就显示什么.

  7. tomcat startup.sh提示java.lang.OutOfMemoryError: PermGen space

    JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m"if [ -z "$LOGGING_MANAGER& ...

  8. Html.Action、html.ActionLink与Url.Action的区别

    1.html.ActionLink返回的指向指定controller.指定action的超链接标签<a>标签.如果没有指定controller,则默认为本页面对应的Controller. ...

  9. iOS 网络与多线程--7.Performselector消息处理方法

    创建一个IOSApp类 IOSApp.h文件 #import <Foundation/Foundation.h> @interface IOSApp : NSObject // 1.添加一 ...

  10. 认识html标签

    让我们通过一个网页的学习,来对html标签有一个初步理解. 平常大家说的上网就是浏览各种各式各样的网页,这些网页都是由html标签组成的. 下面就是一个简单的网页.效果图如下: 我们来分析一下,这个网 ...