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

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

单例模式的实现也有一个循序渐进的过程:
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. Java基础知识强化79:被遗忘的Java Math类

    1. Math类概述 Math类包含用于执行基本数学运算的方法,如初等指数.对数.平方根和三角函数. 2. 成员变量 和 成员方法(常用的) (1)成员变量 public static final d ...

  2. Chapter 7. Dependency Management Basics 依赖管理基础

    This chapter introduces some of the basics of dependency management in Gradle. 7.1. What is dependen ...

  3. SQL语句添加删除修改字段及一些表与字段的基本操作

    用SQL语句添加删除修改字段 1.增加字段     alter table docdsp    add dspcode char(200)2.删除字段     ALTER TABLE table_NA ...

  4. url的非法字符有哪些

    需要过滤的特殊字符及字符串有: net user xp_cmdshell /add exec master.dbo.xp_cmdshell net localgroup administrators ...

  5. MySQL查看数据库大小、表大小和最后修改时间

    查看数据库表基本信息. select * from information_schema.TABLES where information_schema.TABLES.TABLE_SCHEMA = ' ...

  6. iOS 网络与多线程--8.百度地图的使用(调用系统浏览器)

    通过调用设备自带的浏览器,打开百度地图 // 1.定义一个方法,用来打开谷歌地图的功能 -(IBAction)openMaps { // 2.定义一个字符串,作为百度地图的当前地理位置 废弃 NSSt ...

  7. EIGRP认证 配置 (仅仅是命令 原理自己去看书) 转自:http://blog.163.com/s_u/blog/static/13308367201111771831631/

    EIGRP认证 目的:掌握EIGRP的MD5认证 拓扑:这里IP配置我就不写出来了,应该对大家来说是非常简单的事了,就要细心一点就可以了.首先我们在R1上启用MD5认证R1(config)#key c ...

  8. Js判断密码强度并显示提示信息

    用javascipt实现的Ajax判断密码强弱的功能,大多数有用户注册功能的网站,都会有这么一个功能,作为WEB程序员,应该会写这种小模块哦,不懂的就看下这个例子,觉得挺简单,当初帮助了不少人学会了密 ...

  9. python 操作 mysql基础补充

    前言 本篇的主要内容为整理mysql的基础内容,分享的同时方便日后查阅,同时结合python的学习整理python操作mysql的方法以及python的ORM. 一.数据库初探 在开始mysql之前先 ...

  10. Python一路走来 - python基础 数据类型

    对于Python,一切事物都是对象,对象基于类创建 Python数据类型 python主要的数据类型主要包括以下几种类型: (1) 数字型 (2) 字符串 (3) 列表 (4) 元组 (5) 字典 ( ...