Singleton 单例模式 MD
Markdown版本笔记 | 我的GitHub首页 | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
Singleton 单例模式 设计模式 Single
目录
简介
单例模式的几种方式
饿汉式:简单安全,但浪费资源
懒汉式
简单懒汉式:高效,但不安全
加同步锁方式:性能差
双重检查加锁模式:高效且安全
基本形式
存在的安全隐患:指令重排序
添加 volatile 后的终极形式
静态内部类方式:【推荐】
枚举式:天然的防止反射
简介
作用:保证类只有一个实例;提供一个全局访问点
JDK中的案例:
java.lang.Runtime#getRuntime()
单例模式的几种方式
饿汉式:简单安全,但浪费资源
- 优点:以空间换时间。因为静态变量会在类加载时初始化,此时不会涉及多个线程对象访问该对象的问题,虚拟机保证只会加载一次该类,肯定不会发生并发访问的问题,因此,可以省略
synchronized
关键字。
- 问题:如果只是加载本类,而不调用
getInstance()
,甚至永远都不调用,则会造成资源浪费
!
class Single {
private static Single SINGLETON = new Single();//类加载的时候会连带着创建实例
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() {
return SINGLETON;
}
}
懒汉式
简单懒汉式:高效,但不安全
- 优点:以时间换空间。延时加载,懒加载,用的时候才加载,不会像饿汉式那样可能造成资源浪费!
- 问题:在多线程环境下存在风险
class Single {
private static Single SINGLETON;
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() {
if (SINGLETON == null) SINGLETON = new Single();
return SINGLETON;
}
}
synchronized
关键字。getInstance()
,甚至永远都不调用,则会造成资源浪费
!class Single {
private static Single SINGLETON = new Single();//类加载的时候会连带着创建实例
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() {
return SINGLETON;
}
}
class Single {
private static Single SINGLETON;
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() {
if (SINGLETON == null) SINGLETON = new Single();
return SINGLETON;
}
}
线程安全问题出现场景
如果两个线程A和B同时调用了该方法,然后以如下方式执行:
- A进入if判断,此时 instance 为 null,因此进入if内
- B进入if判断,此时A还没有创建 instance,因此 instance 也为 null,因此B也进入if内
- A创建了一个instance并返回
- 虽然此时 instance 不为 null,但因为B已经进入了if判断,所以B也会创建一个instance并返回
- 此时问题出现了,我们的单例被创建了两次!
验证测试代码
验证逻辑很简单,首先在单例的构造方法
中打印一条日志,然后我们创建几十上百个线程,并发的去调用单例模式的getInstance()
方法,通过日志打印来判断到底执行了几次构造方法,依次来验证此种单例模式是否是安全的。
public class Test {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
Single.getInstance();
}
};
for (int i = 0; i < 100; i++) {
new Thread(runnable).start();
}
}
}
经过测试,这种场景下多次创建Single实例并非是小概率事件,反而是大概率事件!
加同步锁方式:性能差
以上问题最直观的解决办法就是给getInstance
方法加上一个synchronize
锁,这样每次只允许一个线程调用getInstance方法:
class Single {
private static Single SINGLETON;
private Single() {
System.out.println("创建了实例");
}
public static synchronized Single getInstance() {
if (SINGLETON == null) SINGLETON = new Single();
return SINGLETON;
}
}
这种解决办法的确可以防止错误的出现,但是它却很影响性能:每次调用getInstance
方法的时候都必须获得Singleton.class
锁!
而实际上,仅仅在创建实例时有线程安全问题,而当单例实例被创建以后,其后的请求没有必要再使用互斥机制了。
双重检查加锁模式:高效且安全
基本形式
目前大量人使用的都是这个double-checked locking
的解决方案:
class Single {
private static Single SINGLETON; //没添加【volatile】关键字之前
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() {
if (SINGLETON == null) { //目的是为了提高性能,避免非必要加锁
synchronized (Single.class) { //加锁保证现场安全
if (SINGLETON == null) SINGLETON = new Single(); //避免重复创建实例
}
}
return SINGLETON;
}
}
让我们来看一下这个代码是如何工作的:
- 当一个线程调用
getInstance()
方法后,首先会先检查SINGLETON是否为null,如果不是则直接返回其内容,这样避免了进入synchronized块所需要花费的资源。 - 其次,即使上面提到的情况发生了,即两个线程同时进入了第一个if判断,那么他们也必须按照顺序执行
synchronized
块中的代码,第一个进入代码块的线程会创建一个新的Single实例,而后续的线程则因为无法通过if判断,而不会创建多余的实例。
存在的安全隐患:指令重排序
上述描述似乎已经解决了我们面临的所有问题,但实际上,从JVM的角度讲,这些代码仍然可能发生错误。
对于JVM而言,它执行的是一个个Java指令。在Java指令中创建对象和赋值操作是分开进行的
,也就是说SINGLETON = new Single();
语句是分两步执行的,但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会先为新的Single实例分配空间,然后直接赋值给SINGLETON,然后再去初始化这个Single实例
。即先赋值指向了内存地址,再初始化
,这样就使出错成为了可能。
我们仍然以A、B两个线程为例:
- A、B线程同时进入了第一个 if 判断
- A首先进入
synchronized
块,由于SINGLETON为null,所以它执行SINGLETON = new Single();
- 由于JVM内部的优化机制,JVM先划出了一些分配给Single实例的
空白内存
,并赋值给SINGLETON成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。 - 然后B进入synchronized块,由于SINGLETON此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
- 此时B线程打算使用Single实例,
却发现它没有被初始化
,于是错误发生了。
下面参考另一篇文章的描述,意思是一样的
问题主要在于SINGLETON = new Single();
这段代码并不是原子操作,原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行(事务)。
SINGLETON = new Single();
其实做了三件事情:
- 给 SINGLETON 实例分配内存
- 初始化 Single() 实例
- 将SINGLETON对象指向 new Single() 分配的内存空间
问题就出在这儿了,因为JVM中有指令重排序的优化,所以呢正常情况按照1,2,3的顺序来,没毛病,但是也可能按照1,3,2的顺序来,这个时候就有问题了,调用的时候判断 SINGLETON != null
就直接返回 SINGLETON 实例,但是这个时候并没有进行初始化工作,所以在后续的调用中肯定就会报错了。
所以这里需要引入了 volatile 修饰符修饰 SINGLETON 对象,因为 volatile 能够禁止指令重排序的功能,所以能解决我们的这个问题
以上情况只是理论分析,实际我经过大量测试发现,这种情况根本展示不出来。但是面试时这个是非常重要的知识点。
添加 volatile 后的终极形式
在JDK1.5
之后,官方也发现了这个问题,故而具体化了 volatile
,即在JDK1.6
及以后,只要定义为private volatile static
就可解决 DCL 失效问题。
volatile 确保 INSTANCE 每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。
class Single {
private static volatile Single SINGLETON; //添加【volatile】关键字
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() {
if (SINGLETON == null) { //目的是为了提高性能,避免非必要获取锁
synchronized (Single.class) { //加锁
if (SINGLETON == null) SINGLETON = new Single(); //线程安全
}
}
return SINGLETON;
}
}
静态内部类方式:【推荐】
JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证INSTANCE只被创建一次,并且会保证把赋值给INSTANCE的内存初始化完毕,这样我们就不用担心上面的问题。
另外,INSTANCE是在第一次加载SingleHolder时被创建的,而SingleHolder则只在调用getInstance方法的时候才会被加载,因此也实现了懒加载。
总结:不会像饿汉式那样立即加载对象,且加载类时是线程安全的,从而兼具了并发高效调用和延迟加载
的优势!
class Single {
private Single() {
System.out.println("创建了实例");
}
public static Single getInstance() { //只有调用getInstance时才会加载静态内部类
return SingleHolder.SINGLETON;
}
private static class SingleHolder {
private static final Single SINGLETON = new Single();
}
}
这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
枚举式:天然的防止反射
线程安全、调用效率高,但不能延时加载,并且可以天然的防止反射和反序列化漏洞!
enum Single {
SINGLETON;//定义一个枚举的元素,它就代表了Single的一个实例。元素的名字随意。
Single() {
System.out.println("创建了实例");
}
}
2017-11-27
Singleton 单例模式 MD的更多相关文章
- singleton单例模式
单例设计模式 单例设计模式概述 单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供 优点: 在系统内存中只存在一个对象,因此可以解决系统资源,对于一些需要频繁 ...
- 设计模式01: Singleton 单例模式(创建型模式)
Singleton 单例模式(创建型模式) 动机(Motivation)当进行软件开发是会有这样一种需求:在系统中只有存在一个实例才能确保它们的逻辑正确性.以及良好的效率.这应该是类设计者的责任,而不 ...
- Singleton单例模式是最简单的设计模式,它的主要作用是保证在程序执行生命周期中,使用了单类模式的类仅仅能有一个实例对象存在。
...
- SingleTon单例模式总结篇
在Java设计模式中,单例模式相对来说算是比较简单的一种构建模式.适用的场景在于:对于定义的一个类,在整个应用程序执行期间只有唯一的一个实例对象. 一,懒汉式: 其特点是延迟加载,即当需要用到此单一实 ...
- (转)Singleton 单例模式(懒汉方式和饿汉方式)
原文地址:http://www.cnblogs.com/kkgreen/archive/2011/09/05/2166868.html 单例模式的概念: 单例模式的意思就是只有一个实例.单例模式确保某 ...
- 单例模式简单解析--Singleton 单例模式(懒汉方式和饿汉方式)
单例模式的概念: 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 关键点: 1)一个类只有一个实例 这是最基本 ...
- Java Singleton 单例模式
大家可能还听过 Singleton 也就是单例模式 这个单例模式要求 在程序的运行时候 一个程序的某个类 只允许产生一个 实例 那么 这个类就是一个单例类 Java Singleton模式主要作 ...
- 【转】Singleton单例模式
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务:一个系统只能有一个窗口管理器或文件系统:一个系统只能有一个计时工具或ID(序号)生成器. ...
- 设计模式C++学习笔记之三(Singleton单例模式)
单例模式看起来也蛮简单的,就是在系统中只允许产生这个类的一个实例,既然这么简单,就直接贴代码了.更详细的内容及说明可以参考原作者博客:cbf4life.cnblogs.com. 3.1.解释 main ...
随机推荐
- 异步任务 -- FutureTask
任务提交 之前在分析线程池的时候,提到过 AbstractExecutorService 的实现: public Future<?> submit(Runnable task) { if ...
- 新一代数据库TiDB在美团的实践
1. 背景和现状 近几年,基于MySQL构建的传统关系型数据库服务,已经很难支撑美团业务的爆发式增长,这就促使我们去探索更合理的数据存储方案和实践新的运维方式.而随着分布式数据库大放异彩,美团DBA团 ...
- 通过Nuget添加Mvvmlight框架发生错误
IDE:Visual Studio 2013 场景:通过Nuget添加Mvvmlight框架 具体错误: 解决办法:删除Nuget,然后添加新版本的Nuget Package Manager 具体操作 ...
- python开发_tkinter_菜单的不同选项
python的tkinter模块中,菜单也可以由你自定义你的风格 下面是我做的demo 运行效果: ====================================== 代码部分: ===== ...
- php中赋值和引用真真的理解
php的引用(就是在变量或者函数.对象等前面加上&符号) //最重要就是 删除引用的变量 ,只是引用的变量访问不了,但是内容并没有销毁 在PHP 中引用的意思是:不同的名字访问同一个变量内容. ...
- Git_简介
Git是什么? Git是目前世界上最先进的分布式版本控制系统(没有之一). Git有什么特点?简单来说就是:高端大气上档次! 那什么是版本控制系统? 如果你用Microsoft Word写过长篇大论, ...
- 模拟拖拽图片 碰撞检测 DOM 鼠标事件 闭包
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" ...
- c++ 常见网络协议头
//NTP协议 typedef struct _NTP_HEADER { uint8_t _flags;//Flags 0xdb uint8_t _pcs;//Peer Clock Stratum u ...
- PostgreSQL修改数据库目录/数据库目录迁移
说明:以9+版本为例,10+的版本只要把目录替换一下即可.迁移目录肯定是要停服的! 1.在数据库软件安装之后,初始化数据库时候,可以指定初始化时创建的数据库的默认文件路径 /usr/local/pgs ...
- 【scrapy】使用方法概要(四)(转)
[请初学者作为参考,不建议高手看这个浪费时间] 上一篇文章,我们抓取到了一大批代理ip,本篇文章介绍如何实现downloaderMiddleware,达到随即使用代理ip对目标网站进行抓取的. 抓取的 ...