一天一个设计模式——(Singleton)单例模式(线程安全性)
一、模式说明
有时候,我们希望在应用程序中,仅生成某个类的一个实例,这时候需要用到单例模式。
二、模式类图

三、模式中的角色
- Singleton角色,该模式中仅有的一个角色,该角色有一个返回唯一实例的getInstance方法,该方法总是返回同一个实例;
四、代码示例
单例模式比较简单,要实现单例模式只需保证三点:
- 私有化类的构造函数;
- 在类中创建类的对象;
- 提供获取对象的公有方法;
package com.designpattern.cn.singletonpattern;
public class Singleton {
private static Singleton singleton = new Singleton();
//也可以使用静态域,在类加载时创建实例
//static {singleton = new Singleton();}
private Singleton(){
System.out.println("Instance created!");
}
public static Singleton getInstance(){
return singleton;
}
}
测试类运行结果如下:

上面的代码中,Singleton类的方法和成员属性都是静态的,原因是我们不能直接创建Singleton类的实例,但是想通过类的方法调用获取实例,因此方法必须是静态的,同时,静态方法只能操作静态成员,所以对象也是静态的。
另外注意到,Singleton类的构造函数被设置为私有的,这样可以避免通过调用new方法来直接创建对象。
五、扩展——几种不同模式的单例模式实现及其线程安全性分析
- 饿汉模式
上面的代码示例中实现的单例模式被称为“饿汉模式”,因为类在加载的过程中,单例就已经初始化完成,确保在获取Instance的时候,实例是已经存在的了。因此饿汉模式是线程安全的单例模式实现,可以直接用于多线程环境。并且由于是在类的加载阶段就生成了实例,因此第一次调用获取对象方法时效率高。缺点是:无论程序最终是否会用到这个类,这个类都会被创建,会占用一定的内存。
- 懒汉模式
与饿汉模式相对的,如果我们在加载类的时候不创建实例,等到第一次调用getInstance方法时才生成实例对象,这种做法就是“懒汉模式”。

懒汉模式相比于饿汉模式,由于是在第一次调用getInstance方法时才通过构造函数创建对象,如果对象的创建代码比较复杂,会影响第一次获取对象的效率。同时,懒汉模式如果不做特殊处理,很明显是线程不安全的,如果要使用懒汉模式又想用在多线程环境中,有几种方式实现线程安全:
1、第一个能想到的方法就是加锁。
package com.designpattern.cn.singletonpattern;
public class LazySingletonThreadLock {
private static LazySingletonThreadLock lazySingletonThreadLock = null;
private LazySingletonThreadLock(){};
public static synchronized LazySingletonThreadLock getInstance(){
if(lazySingletonThreadLock == null){
lazySingletonThreadLock = new LazySingletonThreadLock();
}
return lazySingletonThreadLock;
}
}
这种写法的最大缺点就是效率低!每个线程在想获得类的实例调用getInstance()方法都要进行同步,实际上这个方法只执行一次实例化代码就够了,后面每次获取实例直接return就行了,不用每次都让方法同步。
2、第二个方法:双重校验锁
package com.designpattern.cn.singletonpattern;
public class LazySingletonDoubleCheck {
//双重检查方式
private static volatile LazySingletonDoubleCheck lazySingletonDoubleCheck = null;
private LazySingletonDoubleCheck(){}
public static LazySingletonDoubleCheck getInstance(){
if(lazySingletonDoubleCheck == null){
synchronized (LazySingletonDoubleCheck.class){
if(lazySingletonDoubleCheck == null){
lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
}
}
}
return lazySingletonDoubleCheck;
}
}
这种写法通过两次判断lazySingletonDoubleCheck成员是否为空,从而决定是否创建实例,一旦实例创建后,后续调用获取对象的方法时,直接返回对象,优点:线程安全、延迟加载、高效。
这里必须要插播一下volatile这个java关键字:volatile关键字用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效。volatile具备可见性和有序性,不具备原子性。
- 可见性:多个线程访问一个变量x,A线程修改了变量x的值,其他B线程、C线程。。。立即可以读取到A线程修改x后的值;
- 有序性:即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。而volatile会禁止指令重排,从而保证有序性。
- 原子性:与事务相关的概念,表示一系列操作要么都执行,要么一个也不执行。如果只执行了一部分,那么需要回滚已执行的代码。olatile不具备原子性,这是volatile与java中的synchronized、java.util.concurrent.locks.Lock最大的功能差异。虽然volatile不能保证原子性,但是它也不阻塞线程,因此它的响应速度比synchronized快。volatile适用于读多写少的场景,或者对变量的写操作不依赖于当前值,对变量的读取操作不依赖于非volatile变量的场景。
3、第三种写法:静态内部类
package com.designpattern.cn.singletonpattern;
public class LazySingletonStaticInnerClass {
private LazySingletonStaticInnerClass(){}
//静态内部类
private static class singletonInstance{
private static final LazySingletonStaticInnerClass INSTANCE = new LazySingletonStaticInnerClass();
}
public static LazySingletonStaticInnerClass getInstance(){
return singletonInstance.INSTANCE;
}
}
这种写法比较推荐,这种方式看上去像是饿汉模式,实际上有区别,由于静态内部类的特性,在外部类加载时并不会马上实例化,只有在调用getInstance方法时,才会实例化类。这里,JVM帮助我们保证了线程安全性,当JVM进行类的初始化时,其他线程是无法进入的。保证了线程安全、延迟加载,效率高。
4、第四种写法:利用枚举类(实现单例模式的最佳做法)
创建一个对象的方式有多种:new,克隆,序列化,反射。前三种情况,上面的饿汉模式和线程安全的懒汉模式都可以保证生成实例的唯一性,但是对于最后一种——反射,无法保证实例的唯一性:通过反射可以获取到类的构造方法(即使声明构造方法是private的也没用,反射可以打破一切封装,但是枚举例外)
package com.designpattern.cn.singletonpattern;
public enum SingletonEnum {
INSTANCE;
}
可以使用SingletonEnum.INSTANCE方式获取枚举的实例。由于枚举的构造方式和单例模式很像(构造方法私有化),而且不用考虑序列化等问题。因此使用枚举来构建单例模式是目前最好的做法,并且不被反射打破。(可以参考阅读《Effective Java》)
六、相关的模式
- 抽象工厂(AbstractFactory)模式
- Builder模式
- Fcade外观模式
- Prototype原型模式
一天一个设计模式——(Singleton)单例模式(线程安全性)的更多相关文章
- [C++设计模式] singleton 单例模式
这个设计模式主要目的是想在整个系统中仅仅能出现一个类的实例.这样做当然是有必定的.比方你的软件的全局配置信息,或者是一个Factory,或是一个主控类,等等. 你希望这个类在整个系统中仅仅能出现一个实 ...
- 设计模式—singleton(单例模式)
单例模式 单例设计模式属于创建型模式,它提供了一种创建对象的最佳方式.这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建. 这个类提供了一种访问其唯一的对象的方式,可以直接 ...
- C++设计模式-singleton单例模式_new
class nocopyable { protected: nocopyable(){}; virtual ~nocopyable(){}; nocopyable(const nocopyable ...
- C++设计模式-Singleton单例模式
template <typename T> class Singleton { public: template <typename... Args> static T* In ...
- 设计模式一: 单例模式(Singleton)
简介 单例模式是属于创建型模式的一种(另外两种分别是结构型模式,行为型模式).是设计模式中最为简单的一种. 英文单词Singleton的数学含义是"有且仅有一个元素的集合". 从实 ...
- 设计模式之单例模式(Singleton)
设计模式之单例模式(Singleton) 设计模式是前辈的一些经验总结之后的精髓,学习设计模式可以针对不同的问题给出更加优雅的解答 单例模式可分为俩种:懒汉模式和饿汉模式.俩种模式分别有不同的优势和缺 ...
- 每天一个设计模式-4 单例模式(Singleton)
每天一个设计模式-4 单例模式(Singleton) 1.实际生活的例子 有一天,你的自行车的某个螺丝钉松了,修车铺离你家比较远,而附近的五金店有卖扳手:因此,你决定去五金店买一个扳手,自己把螺丝钉固 ...
- 设计模式之单例模式——Singleton
设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...
- 设计模式之单例模式Singleton(三创建型)
1.什么事单例模式? 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须 ...
随机推荐
- java 垒骰子
垒骰子 赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体. 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰子:1 的 ...
- P1079 延迟的回文数
P1079 延迟的回文数 转跳点:
- exchange 强制更新全球通讯簿
————-客户端强制更新方式————– Outlook通讯录默认情况需要2-3天同步更新通讯录,可以使用下列方式立即更新通讯录 1. 关闭outlook ,打开下列文件夹 %userprofile%\ ...
- tan?
痰是一种急.慢性气管--支气管炎,咳.痰.喘.炎是下呼吸道感染的常见主征.下呼吸道感染有急性和慢性之分.急性感染主要的是急性气管--支气管炎,是呼吸系统最常见的一种疾病,多由感染.物理化学刺激或过敏引 ...
- java内部类 2.19
1.定义了成员内部类后,必须使用外部类对象来创建内部类对象,而不能直接去 new 一个内部类对象,即:内部类 对象名 = 外部类对象.new 内部类( ); //外部类HelloWorld publi ...
- /etc/fstab 只读无法修改的解决办法
在做saltstack的时候不小心误把/etc/fstab给注释了 在命令补全的时候开始报错:[root@kafka2 ~]# cat /et-bash: cannot create temp fil ...
- VS2012 ffmpeg 没有定义的lrint
在common.h #ifndef AVUTIL_COMMON_H#define AVUTIL_COMMON_H下添加 #define lrint(f) (f>=0?(int32_t)(f+(d ...
- 洛谷 P2549 计算器写作文
题目传送门 解题思路: 背包,f[i]表示计算器位数为i时,可获得的最大分值. 本题与01背包不同的地方在于,物品的摆放顺序对答案是有影响的,例如两个字符串a,b,那么就会出现a+b和b+a两种情况( ...
- CSS - flex 垂直水平居中
display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */
- 从0到1完成微信小程序开发(2)
一,小程序的文件结构 小程序包含一个描述程序的app和多个描述各自页面的page 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下: 一个小程序页面由四个文件组成,分别是: 下面是一个单页 ...