[Java] [Singleton] [DCL][happens-before]
Singleton
- 只能有一个实例;必须自己创建自己的实例;必须给其他所有对象提供这一实例
实现方法
饿汉式singleton
- 预先加载法
class Single {
private Single() {
System.out.println("ok");
} private static Single instance = new Single(); public static Single getInstance() {
return instance;
}
}- 优点:
- thread safe
- 调用时速度快(在类加载时已经创建好一个static对象)
- 缺点:
- 资源利用率不高(可能系统不需要)
- 在一些场景下无法使用。比如在single实例的创建依赖参数或配置文件时。
懒汉式singleton
- 延迟加载法
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {} public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}- 适用于单线程环境,not trhead-safe,getInstance()方法可能返回两个不同实例。
- 可以改成thread-safe版本,如下:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {} public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
} - 优点:不执行getInstance对不会被实例化
- 缺点:第一次加载时反应不快。每次调用getInstance的同步开销大。(大量不必要的同步)
DCL singleton
- Double Check Lock
- 避免每次调用getInstance方法时都同步
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {} public static LazySingleton getInstance() {
if (instance == null) {
synchronized(LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}- 第一层判断,避免不必要的同步。第二层判断则是在线程安全的情况下创建实例。
- 优点:资源利用率高,多线程下效率高。
- 缺点:第一次加载时反应不快,由于java内存模型一些原因偶尔会失败,在高并发下有一定的缺陷。
- 上述代码依然存在不安全性:
instance = new LazySingleton()这条语句实际上不是一个原子操作,它大概包括三件事:- 给LazySingleton的实例分配内存;
- 初始化LazySingleton()的构造器;
- 将instance对象指向分配的内存空间(在这一步的时候instance变成非null)。
但是由于Java编译器允许处理器乱序执行(指令重排序),上述2、3点的顺序是无法保证的。(意思是可能instance != null时有可能还未真正初始化构造器)。
解决方法是通过将instance定义为volatile的。(volatile有两个语义:1. 保证变量的可见性;2. 禁止对该变量的指令重排序)
- 参考<<Java并发编程>> P286 ~ P287。在JMM后续版本(>= Java5.0)中,可以通过结合volatile的方式来启动DCL,并且该方式对性能的影响很小。然而,DCL的这种使用方式已经被广泛地抛弃了。
- (因为volatile屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中即使将变量声明为volatile,也仍然不能完全避免重排序所导致的问题,这主要是因为volatile变量前后的代码仍然存在重排序问题。)
- (Java5中多了一条happens-before的规则:对volatile字段的写操作happens-before后续对同一个字段的读操作)
static内部类singleton
class Single {
private Single() {} private static class InstanceHolder {
private static final Single instance = new Single();
} public static Single getInstance() {
return InstanceHolder.instance();
}
}- 优点:线程安全,资源利用率高。
- 缺点:第一次加载时反应不快。
- 原理:类级内部类(static修饰的成员内部类)只有在第一次使用时才会被加载。
Summary
- 考虑到效率、安全性等问题,一般常用饿汉式singleton or static内部类singleton。其中后者是常用的singleton实现方法。
Happens-before
- 是否可以通过几个基本的happens-before规则从理论上分析Java多线程程序的正确性,而不需要设计到硬件和编译器的知识呢?
Happens-before规则
- 通俗来说,A happens-before B意味着操作A对内存施加的影响都能被B观测到。
- 关于happens-before:
- happens-before relation on memory operations such reads and writes of shared varaiables.
- In particular:
- Each action in a thread happens-before every action in that thread that comes later in the program's order. (单线程规则)
- An unlock (
synchronized
block or method exit) of a monitor happens-before every subsequent lock (synchronized
block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor. (线程安全性主要依赖这条规则) - A write to a
volatile
field happens-before every subsequent read of that same field. Writes and reads ofvolatile
fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking. - A call to
start
on a thread happens-before any action in the started thread. - All actions in a thread happen-before any other thread successfully returns from a
join
on that thread. - happens-before关系具有传递性。 hb(A, B) + hb(B, C) => hb(A, C)
- 要知道,“A在时间上先于B”和“A happens-before B”两者并不等价。
- 两个操作之间必然存在某种时序关系(先后or同时),但两个操作之间却不一定存在happens-before关系。但两个存在happens-before关系的操作不可能同时发生,这实际上也是同步的语义之一(独占访问)。
- 以及,上述一直提到的操作并不等同于语句。操作应该是单个虚拟机指令,单条语句可能由多个指令组成。
Happens-before & DCL
- DCL(without volatile)的主要问题在于尽管得到了LazySingleton的引用,但却有可能访问到其成员变量的不正确值。
- 重新分析上述DCL例子:
public class LazySingleton {
private int someField; private static LazySingleton instance; private LazySingleton() {
this.someField = new Random().nextInt(200)+1; // (1)
} public static LazySingleton getInstance() {
if (instance == null) { // (2)
synchronized(LazySingleton.class) { // (3)
if (instance == null) { // (4)
instance = new LazySingleton(); // (5)
}
}
}
return instance; // (6)
} public int getSomeField() {
return this.someField; // (7)
}
} - DCL产生安全问题的主要原因就在于:(1) & (7) 之间不存在happens-before关系。
- 这个例子中LazySingleton是一个不变类,它只有get而没有set方法。但上述例子让我们知道,即使一个对象是不变的,在不同的线程中也可能返回不同值。这是因为LazySingleton没有被安全地发布。
[Java] [Singleton] [DCL][happens-before]的更多相关文章
- Java Singleton 单例模式
大家可能还听过 Singleton 也就是单例模式 这个单例模式要求 在程序的运行时候 一个程序的某个类 只允许产生一个 实例 那么 这个类就是一个单例类 Java Singleton模式主要作 ...
- Java Singleton的3种实现方式
1.通过静态成员字段来实例化 public class Elvis { /** * 通过final的静态成员字段来调用私有的构造函数实例化对象 */ public static final Elvis ...
- Java Singleton(单例模式) 实现详解
什么是单例模式? Intend:Ensure a class only has one instance, and provide a global point of access to it. 目标 ...
- Java并发——DCL问题
转自:http://www.iteye.com/topic/875420 如果你搜索网上分析dcl为什么在java中失效的原因,都会谈到编译器会做优化云云,我相信大家看到这个一定会觉得很沮丧.很无助, ...
- Java singleton 一例
org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of ...
- Java Singleton Implementation
概述 Java中单例模式的实现有多重方法, 要实现单例模式主要的问题是线程安全问题以及对Lazy Load的考虑,主要有如下几种 双重锁定懒加载单例 预加载单例 枚举单例 双重锁定懒加载单例模式 /* ...
- java singleton(单例设计模式)
单例设计模式的主要作用是: 1.控制资源的使用,我们对资源使用线程同步来实现并发访问. 2.节约资源,我们对一个类只进行一个实例化进行全局的资源访问,节约了内存. 3.作为通信媒介,也是数据共享,可以 ...
- Java singleton 单例
饿汉式,instance在类加载化时完成初始化,线程安全 package cookie; public class SingletonAtOnce { private SingletonAtOnce( ...
- The Java Enum: A Singleton Pattern [reproduced]
The singleton pattern restricts the instantiation of a class to one object. In Java, to enforce this ...
随机推荐
- Python3爬虫相关软件,库的安装
Anaconda 百度搜Anaconda清华,根据环境选择版本下载 安装时记得勾选添加到环境变量,不要还要手动添加 Anaconda Navigator可视化界面,可以方便地调用Jupyter等工具. ...
- vim实现实时自动保存
进https://www.vim.org/scripts/script.php?script_id=4521网站下载vim -auto-save wget https://www.vim.org/s ...
- 简单谈谈$.merge()
var arr1 = [1,2,3]; var arr2 = [1,2,3]; console.log($.merge(arr1,arr2)) //[1,2,3,1,2,3],可见数组间只是合并,不会 ...
- HDU 5299 Circles Game
HDU 5299 思路: 圆扫描线+树上删边博弈 圆扫描线有以下四种情况,用set维护扫描线与圆的交点,重载小于号 代码: #pragma GCC optimize(2) #pragma GCC op ...
- springboot 启动报错
有一个警告 :** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of the d ...
- discuss!X3.4 帖子显示昵称而不是用户名的解决办法
问题:dedecmsV5.7和discuz!X3.4整合之后,实现免激活登陆之后,从dede过来的用户在discuz 直接展示的用户名,因为我们的用户名是手机号,所以不想帖子都展示的是用户名. 因为我 ...
- Jupyter Notebook 修改默认打开的文件夹的位置
初次使用Jupyter Notebook,确实好用啊!!,又好看又好用,不过还是遇到了一个问题,安装好之后,打开Jupyter Notebook 的时候,默认的文件夹的位置是C盘下面的XXX目录,但是 ...
- C# 异步(上)
新进阶的程序员可能对async.await用得比较多,却对之前的异步了解甚少.本人就是此类,因此打算回顾学习下异步的进化史. 本文主要是回顾async异步模式之前的异步,下篇文章再来重点分析async ...
- Linux—shell中$(( ))、$( )、``与${ }的区别
命令替换 在bash中,$( )与` `(反引号)都是用来作命令替换的.命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行. exp 1 [ ...
- Chromedriver executable needs to be in path 解决办法
执行webdriver.Chrome()时报错:Chromedriver executable needs to be in path. 原因可能是为有安装Chromedriver 可能是Chrome ...