背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的。

例子1:没有volatile修饰的uniqueInstance

public class Singleton {
private static Singleton uniqueInstance; private Singleton(){
} public static Singleton getInstance(){
if(uniqueInstance == null){ //#1
synchronized(Singleton.class){ //#2
if(uniqueInstance == null){ //#3
uniqueInstance = new Singleton(); //#4
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
} else {
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
}
}
}
return uniqueInstance;
}
}
 public class TestSingleton {
public static void main(final String[] args) throws InterruptedException {
for (int i = 1; i <= 100000; i++) {
final Thread t1 = new Thread(new ThreadSingleton());
t1.setName("thread" + i);
t1.start();
}
} public static class ThreadSingleton implements Runnable {
@Override
public void run() {
Singleton.getInstance();
}
}
}

这里面的结果有可能会是:(没有真正重现过,太难模拟了)

 thread2: uniqueInstance is initalized...
thread3: uniqueInstance is initalized...
Singleton被实例化两次了,和我们的单例模式设计期望值不一致:类永远只被实例化一次.

原因分析:
1. thread2进入#1, 这时子线程的uniqueInstance都是为空的,thread2让出CPU资源给thread3
2. thread3进入#1, 这时子线程的uniqueInstance都是为空的, thread3让出CPO资源给thread2
3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance。thread2执行完毕让出CPO资源给thread3
4. thread3接着#1跑下去,跑到#3的时候,由于#1里面拿到的uniqueInstance还是空(并没有及时从thread2里面拿到最新的),所以thread3仍然会执行#4,#5.1
5. 最后在thread2和thread3都实例化了uniqueInstance

例子2:用volatile修饰的uniqueInstance

这里就不贴重复的代码了,因为只是加多一个volatile来修饰成员变量:uniqueInstance,

但是结果却是正确的了, 其中一个可能结果:

thread2: uniqueInstance is initalized
thread3: uniqueInstance is not null now...

原因分析:

volatile(java5):可以保证多线程下的可见性;

读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。

写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

1. thread2进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2),thread2让出CPU资源给thread3
2. thread3进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2), thread3让出CPO资源给thread2
3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance(由于是volatile修饰的变量,会马上同步到主线程的变量去)。thread2执行完毕让出CPO资源给thread3
4. thread3接着#1跑下去,跑到#3的时候,会又一次从主线程拷贝一份uniqueInstance!=null回来,所以thread3就直接跑到了#5.2
5. 最后在thread3不再会重复实例化uniqueInstance了

参考文章:如何在Java中使用双重检查锁实现单例

官方文档说明

深入理解Java内存模型(一)——基础

双重检查锁定与延迟初始化

单例模式中用volatile和synchronized来满足双重检查锁机制的更多相关文章

  1. 从学习“单例模式”学到的Java知识:双重检查锁和延迟初始化

    一切真是有缘,上午刚刚看完单例模式,还在为其中的代码块同步而兴奋,下午就遇见这篇文章:双重检查锁定与延迟初始化.我一看,文章开头语出惊人,说这是一种错误的优化,我说,难道上午学的东西下午就过时了吗?仔 ...

  2. 对象部分初始化:原理以及验证代码(双重检查锁与volatile相关)

    对象部分初始化:原理以及验证代码(双重检查锁与volatile相关) 对象部分初始化被称为 Partially initialized objects / Partially constructed ...

  3. Java基础教程:多线程杂谈——双重检查锁与Volatile

    Java基础教程:多线程杂谈——双重检查锁与Volatile 双重检查锁 有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实 ...

  4. 【Java学习笔记】线程安全的单例模式及双重检查锁—个人理解

    搬以前写的博客[2014-12-30 16:04] 在web应用中服务器面临的是大量的访问请求,免不了多线程程序,但是有时候,我们希望在多线程应用中的某一个类只能新建一个对象的时候,就会遇到问题. 首 ...

  5. Java中的双重检查锁(double checked locking)

    最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...

  6. 双重检查锁实现单例(java)

    单例类在Java开发者中非常常用,但是它给初级开发者们造成了很多挑战.他们所面对的其中一个关键挑战是,怎样确保单例类的行为是单例?也就是说,无论任何原因,如何防止单例类有多个实例.在整个应用生命周期中 ...

  7. C++的双重检查锁并不安全(转)

    一个典型的单例模式构建对象的双重检查锁如下: static Singleton * getSingleObject() { if(singleObject==NULL) { lock(); if(si ...

  8. 双重检查加锁机制(并发insert情况下数据重复插入问题的解决方案)

    双重检查加锁机制(并发insert情况下数据重复插入问题的解决方案) c#中单例模式和双重检查锁 转:https://blog.csdn.net/zhongliangtang/article/deta ...

  9. 双重检查锁单例模式为什么要用volatile关键字?

    前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下v ...

随机推荐

  1. C#开发微信门户及应用(4)--关注用户列表及详细信息管理

    在上个月的对C#开发微信门户及应用做了介绍,写过了几篇的随笔进行分享,由于时间关系,间隔了一段时间没有继续写这个系列的博客了,并不是对这个方面停止了研究,而是继续深入探索这方面的技术,为了更好的应用起 ...

  2. csharp: Download SVN source

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  3. 关于gdb和shp的FID问题

    gdb的FID从1开始,并且FID唯一,从数字化时开始,每个图形对应唯一的FID,删除图形亦删除对应的FID.FID可能出现中断的情况. shp的FID从0开始,并且永远连续.删除图形,则编号在其下面 ...

  4. angular2 service component

    [component 需要通过 service 提供的接口 得到一些数据.这是最佳实践.] [由于 有 component 和 service 两个语义,所以出现了下面两种办法] 一,[service ...

  5. redis的安装配置

    主要讲下redis的安装配置,以及以服务的方式启动redis 1.下载最新版本的redis-3.0.7  到http://redis.io/download中下载最新版的redis-3.0.7 下载后 ...

  6. 移动Web利器transformjs入门

    简介 在过去的两年,越来越多的同事.朋友和其他不认识的童鞋进行移动web开发的时候,都使用了transformjs,所有必要介绍一下,让更多的人受益,提高编程效率,并享受编程乐趣.(当然transfo ...

  7. 深入理解javascript函数参数与闭包(一)

    在看此文章,希望先阅读关于函数基础内容 函数定义与函数作用域 的章节,因为这篇文章或多或少会涉及函数基础的内容,而基础内容,我放在函数定义函数作用域 章节. 本文直接赘述函数参数与闭包,若涉及相关知识 ...

  8. CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout使用

    本文介绍Design Support Library中CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout的使用. 先列出了Design S ...

  9. Android开发学习—— ContentProvider内容提供者

    * 应用的数据库是不允许其他应用访问的* 内容提供者的作用就是让别的应用访问到你的数据库.把私有数据暴露给其他应用,通常,是把私有数据库的数据暴露给其他应用. Uri:包含一个具有一定格式的字符串的对 ...

  10. Git从码云Clone代码到本地

    Git从码云或者Github 克隆代码到本地 1.下载安装Git,傻瓜式下一步下一步即可... 2.配置Git: 2.1.选择你要clone到本地的路径:右键--->$ Git Bash Her ...