单例模式到Java内存模型
先说单例模式:
经典的单例模式实现:
饿汉式:
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
懒汉式:
public class Singleton {
private static Singleton instance = null;
synchronized public static Singleton getInstance(){
if(instance ==null){
instance = new Singleton();
}
return instance;
}
}
这两种都是可以安全运行在多线程下的。但是每一个都有点缺点,对于第一种如果这个单例的初始化需要很多内存和时间,我们希望用到时在初始化,没有用到就不初始化。对于第二种我们,其实只需要在第一次初始化时需要避免线程冲突,其他时候都可以直接返回的,而第二种的实现则变成了完全的串行(因为每一个操作都需要获得对象锁),非常大的降低了并发度。
我们尝试以下改进:
一个好的方式是DCL(double-checked locking),这种方式的实现如下:
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
这样看起来是完美的解决方案,确实DCL是一个很好的解决思想,在C下其能很好的运行,但在Java下就会有问题了。这全怪Java的JMM(Java内存模型)。
在执行到instance = new Singleton()这里时,由于Java内存的“无序写入”,
可能的执行顺序是这样的:
mem = allocate(); //Allocate memory for Singleton object.
instance = mem; //Note that instance is now non-null, but has not been initialized.
ctorSingleton(instance); //Invoke constructor for Singleton passinginstance.
即为instance分配内存,标记instance不为空,初始化instance。
这样会导致,一个线程刚标记完,还没有初始化赋值给instance,就释放了锁,然后另一个线程进入锁,判断不为空,释放锁,返回instance,这时显然是错的。这里出现这中错误的原因是instance = new instance();并没有真正的执行完,就释放了锁,我实在不能理解这样设计的原因,但很好的是在JDK1.5之后,已不存在这种问题了DCL这个可以很好的运行。但我们还是有必要继续讨论JDK1.5之前如何实现的。
可以在instance返回之前加一个步奏,确定其确实初始化了。
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
Singleton temp = instance;
synchronized (Singleton.class) {
if (temp == null)
instance = new Singleton();
}
instance = temp;
}
return instance;
}
}
这样就很好的解决这个问题了。但是代码量和可阅读性已经陡然上升了,那么有没有更好的方法呢?是有的,利用类加载机制来实现,延迟初始化。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
这里补充一点类加载的知识,类加载分为一个步骤,加载->验证->准备->解析->初始化->使用->卸载。
Java中对何时初始化一个类有严格的说明,这里涉及到的一个规则是当类的静态域,或静态方法被引用的时候,必须对声明这个静态域或方法的类进行初始化。至于说明时候对类进行加载,这个有两种形式:饿加载(只要有其他类引用了它就加载),懒加载(初始化的时候才加载)。具体JVM对这点的实现不同。
所以当懒汉式能保证用到的时候才进行初始化,而饿汉试则是在加载时就初始化了。
单例模式到Java内存模型的更多相关文章
- 转:【Java并发编程】之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...
- Java内存模型及Java关键字 volatile的作用和使用说明
先来看看这个关键字是什么意思:volatile [ˈvɒlətaɪl] adj. 易变的,不稳定的; 从翻译上来看,volatile表示这个关键字是极易发生改变的.volatile是java语言中, ...
- 【Java并发编程】之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...
- 多线程系列八:线程安全、Java内存模型(JMM)、底层实现原理
一.线程安全 1. 怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...
- Java内存模型(一)
主存储器和工作存储器 Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域,这些区域包括方法区,堆,虚拟机栈,本地方法栈,程序计数器.方法区存储类信息,常量,字节码等数据 ...
- 【Java并发编程】:深入Java内存模型——happen-before规则及其对DCL的分析
happen—before规则介绍 Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其意思就是说, ...
- 07 volatile & java 内存模型
一 从单例模式说起 在singleton 单例模式一文中我们详细了解Java中单例模式的实现,不了解的可以先阅读之. 在该文最后我们给出了双重校验锁来保证既实现线程安全,又能够使性能不受很大的影响的单 ...
- jvm(12)-java内存模型与线程
[0]README 0.1)本文部分文字描述转自“深入理解jvm”,旨在学习“java内存模型与线程” 的基础知识: [1]概述 1)并发处理的广泛应用是使得 Amdahl 定律代替摩尔定律称为计 ...
- JAVA内存模型与线程以及volatile理解
Java内存模型是围绕在并发过程中如何处理原子性.可见性.有序性来建立的. 一.主内存与工作内存 Java内存模型主要目标是在虚拟机中将变量存储到内存和从内存中取出变量.这里的变量包括:实例字段.静态 ...
随机推荐
- public interface Sampler extends Serializable, TestElement
在看jmeter源码时看到的,当时就傻了,不能啊,java中只能是单继承啊,呃,其实是类只能是单继承,而接口可以多继承,不要以为extends后边跟的都是类 Serializable, TestEle ...
- php if判断
php if判断 例子如下: True是否等于False 变量haq是不是老婆呢? <?php $ts=true; $f=false; if (isset($ts)&&isset ...
- django之中间件设置
中间件 是一个轻量级.底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出 激活:添加到Django配置文件中的MIDDLEWARE_CLASSES元组中 每个中间件 ...
- sublime中开启表格插入
1. 导入包 tableedit 2. ctrl+shift +p 输入teecv,然后开启后面连个东西: 3 按tab可以插入表格
- scrapy框架的持久化存储
一 . 基于终端指令的持久化存储 保证爬虫文件的parse方法中有可迭代类型对象(通常为列表or字典)的返回,该返回值可以通过终端指令的形式写入指定格式的文件中进行持久化操作. 执行输出指定格式进行存 ...
- django -- url (模版语言 {% url 'test1' param1=5 param2=6 %})
如果想让form表单提交的url是类似 action="/index-5-6.html" 这样的,可以在html模版语言中使用{% url 'test1' param1=5 par ...
- Delphi 画箭头
procedure TForm1.Line(x, y, x2, y2: integer); begin canvas.MoveTo(x, y); canvas.LineTo(x2, y2); end; ...
- 关于BigDecimal类型在jsp页面中进行除法运算问题
出自:http://blog.csdn.net/u011910290/article/details/52935337 问题描述: 在项目编写过程中,发现BigDecimal在jsp中进行除法运算时, ...
- 无法访问win8默认共享(如C$)解决办法
可以使用此过程允许作为本地 Administrators 组的成员并使用密码身份验证登录的用户在会话过程中使用其管理权限.启动注册表编辑器.单击“开始”,在“开始搜索”框中键入 regedit,然后按 ...
- mybatis与springdata的一些简单比较与思考
主题 最近在用mybatis做项目,有一些感触想记录下,主要是mybatis(以及它的一些插件)相比较于Spring data(或者jpa,hibernate等)的优势地方. 感触 我觉得mybati ...