关于并发场景下,通过双重检查锁实现延迟初始化的优化问题隐患的记录

  首先,这个问题是从《阿里巴巴Java开发手册》的1.6.12(P31)上面看到的,里面有这样一句话,并列出一种反例代码(以下为仿写,并非与书上一致):

  在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患,推荐解决方案中较为简单的一种(适用于JDK5及以上的版本),即目标属性声明为volatile型。

  

 1 public class Singleton {
 2     private static Singleton instance=null;
 3     private Singleton() {
 4     }
 5
 6     public static Singleton getInstance() {
 7         if (instance == null) {//1
 8             synchronized (Singleton.class) {//2
 9                 if (instance == null) {//3
10                     instance = new Singleton();//4
11                 }
12             }
13         }
14         return instance;
15     }
16 }

  这样的单例模式经常容易看到(我之前也是这样写的),这样的问题在于初始化代码:

     instance = new Singleton();

JVM会将这段代码分成三步去执行:

  a.分配内存空间;

  b.构造Singleton;

  c.将instance指向构造的实例。

  如果执行的过程是a->b->c的话,那上面的代码是没有问题的,但是有时JVM会基于指令优化的目的将指令重排,导致指令执行流程变为a->c->b。这样当线程A执行到4开始初始化单例对象的c流程时,线程B执行到1处,由于instance对象已经将内部指针指向分配的内存空间(即不为null),会直接返回未完全构造好的实例,从而出错。按照《手册》的说法,修改后的代码如下

 1 public class Singleton {
 2     private static volatile Singleton instance=null;    //添加volatile修饰符
 3     private Singleton() {
 4     }
 5
 6     public static Singleton getInstance() {
 7         if (instance == null) {//1
 8             synchronized (Singleton.class) {//2
 9                 if (instance == null) {//3
10                     instance = new Singleton();//4
11                 }
12             }
13         }
14         return instance;
15     }
16 }

  由于volatile自带的“禁止指令重优化”语义,初始化语句只能按照a->b->c的顺序进行执行。详细的解释可以参考这篇文章:《Java单例模式中双重检查锁的问题》

  

注:尽管这个问题看起来很简单,但是我在本地没有办法重演这个bug,这个bug出现的关键时刻在于线程A在执行a->c->b链的c时,线程B将构造完的instance返回并使用才会出错,但是一般的场景下是没有办法在这么短的时间间隔内捕获到这个间隔的。不过出于保险的目的,单例模式的我还是加上volatile修饰符比较好。

  

原文地址:https://www.cnblogs.com/oreo/p/8505005.html

时间: 2024-11-06 18:41:32

关于并发场景下,通过双重检查锁实现延迟初始化的优化问题隐患的记录的相关文章

高并发场景下秒杀项目静态锁的使用疑问

题:高并发场景下秒杀项目静态锁的使用疑问场景:我们有一个秒杀平台,可以提供所有接入公司创建的秒杀活动,简单描述如下:1.秒杀10袋洗衣粉,开始时间12:00(项目ID:A001)2.秒杀iPhone5,开始时间12:00(项目ID:A002)3.秒杀水杯,开始时间12:00(项目ID:A003)... ...(项目ID:A004-A009)10.秒杀ThinkPad,开始时间12:00(项目ID:A010) 例如上面,同时有十个秒杀,都是12:00整开始,每个秒杀之间没有任何关系. 按照我之前的

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

在Java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实现线程安全的延迟初始化需要一些技巧,否则很容易出现问题.比如,下面是非线程安全的延迟初始化对象的示例代码: public class UnsafeLazyInitialization { private static Instance instance; public static Instance getInstance() { if (instanc

JAVA 双重检查锁定和延迟初始化

双重检查锁定的由来在Java程序中,有时需要推迟一些高开销的对象的初始化操作,并且只有在真正使用到这个对象的时候,才进行初始化,此时,就需要延迟初始化技术.延迟初始化的正确实现是需要一些技巧的,否则容易出现问题,下面一一介绍. 方案1 public class UnsafeLazyInit{ private static Instance instance; public static Instance getInstance(){ if (instance == null){ instance

为什么双重检查锁模式需要 volatile ?

双重检查锁定(Double check locked)模式经常会出现在一些框架源码中,目的是为了延迟初始化变量.这个模式还可以用来创建单例.下面来看一个 Spring 中双重检查锁定的例子. 这个例子中需要将配置文件加载到 handlerMappings中,由于读取资源比较耗时,所以将动作放到真正需要 handlerMappings 的时候.我们可以看到 handlerMappings 前面使用了volatile .有没有想过为什么一定需要 volatile?虽然之前了解了双重检查锁定模式的原理

高并发场景下锁

如何确保一个方法,或者一块代码在高并发情况下,同一时间只能被一个线程执行,单体应用可以使用并发处理相关的 API 进行控制,但单体应用架构演变为分布式微服务架构后,跨进程的实例部署,显然就没办法通过应用层锁的机制来控制并发了.那么锁都有哪些类型,为什么要使用锁,锁的使用场景有哪些?今天我们来聊一聊高并发场景下锁的使用技巧. 锁类别 不同的应用场景对锁的要求各不相同,我们先来看下锁都有哪些类别,这些锁之间有什么区别. 悲观锁(synchronize) Java 中的重量级锁 synchronize

单例陷阱——双重检查锁中的指令重排问题

之前我曾经写过一篇文章<单例模式有8种写法,你知道么?>,其中提到了一种实现单例的方法-双重检查锁,最近在读并发方面的书籍,发现双重检查锁使用不当也并非绝对安全,在这里分享一下. 单例回顾 首先我们回顾一下最简单的单例模式是怎样的? /** *单例模式一:懒汉式(线程安全) */ public class Singleton1 { private static Singleton1 singleton1; private Singleton1() { } public static Singl

从单例的双重检查锁想到的

常说的单例有懒汉跟饿汉两种写法.饿汉由于类加载的时候就创建了对象,因此不存在并发拿到不同对象的问题,但会由于开始就加载了对象,可能会造成一些启动缓慢等性能问题:而懒汉虽然避免了这个问题,但普通的写法会在高并发环境下创建多个对象,单纯加synchronize又会明显降低并发效率,较好的两种写法是静态内部类跟双重检查锁两种. 双重检查锁这个,大家都很熟悉了,上代码: public class SingleTest { private static SingleTest singleTest; //获

缓存在高并发场景下的常见问题

缓存一致性问题 当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象.这就比较依赖缓存的过期和更新策略.一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存. 缓存并发问题 缓存过期后将尝试从后端数据库获取数据,这是一个看似合理的流程.但是,在高并发场景下,有可能多个请求并发的去从数据库获取数据,对后端数据库造成极大的冲击,甚至导致 “雪崩”现象.此外,当某个缓存key在被更新时,同时也可能被大量请求在获取,

单例模式中用volatile和synchronized来满足双重检查锁机制

背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的. 例子1:没有volatile修饰的uniqueInstance public class Singleton { private static Singleton uniqueInstance; private Singleton(){ } public static Singleton getInsta