精华内容
参与话题
问答
  • 双重检查锁

    2018-12-04 18:23:47
    单例模式下标准模式的双重检查锁 /** * @author Yuukii * @since 2018/12/4 */ public class Singleton { private volatile static Singleton instance = null; private Singleton() { } public static ...

    单例模式下标准模式的双重检查锁

    /**
     * @author Yuukii
     * @since 2018/12/4
     */
    public class Singleton {
        private volatile static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (null == instance) {                    //第一次检查
                synchronized (Singleton.class) {       //加锁
                    if (null == instance) {            //第二次检查
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    1. 使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。
    2. 执行双重检查是因为如果存在多个线程同时通过了第一次检查,并且其中一个线程首先通过了第二次检查并实例化了对象,那么剩余通过了第一次检查的线程就不会再去实例化对象。从而保证了单例模式的实现。
    3. 调用时示例:
     Singleton.getInstance().method();//调用

     

    展开全文
  • JAVA锁——双重检查锁

    2020-11-18 22:25:32
    双重检查锁(double checked locking) 文章目录双重检查锁(double checked locking)概述一、不考虑多线程情况下,实现单例模式二、添加synchronized锁,解决线程安全问题三、添加双重检查锁,解决锁资源浪费问题...

    双重检查锁(double checked locking)



    概述

    双重检查锁(double checked locking)是对上述问题的一种优化。先判断对象是否已经被初始化,再决定要不要加锁。


    一、不考虑多线程情况下,实现单例模式

    public class Singleton {
        private static Singleton uniqueSingleton;
    
        private Singleton() {
        }
    
        public Singleton getInstance() {
            if (null == uniqueSingleton) {
                uniqueSingleton = new Singleton();
            }
            return uniqueSingleton;
        }
    }
    

    在多线程的情况下,这样写可能会导致uniqueSingleton有多个实例。比如下面这种情况,考虑有两个线程同时调用getInstance()

    Time Thread A Thread B
    T1 检查到uniqueSingleton为空
    T2 检查到uniqueSingleton为空
    T3 初始化对象A
    T4 返回对象A
    T5 初始化对象B
    T6 返回对象B

    产生问题
    多线程时,容易初始化多次,线程不安全。

    二、添加synchronized锁,解决线程安全问题

    public class Singleton {
        private static Singleton uniqueSingleton;
    
        private Singleton() {
        }
    
        public synchronized Singleton getInstance() {
            if (null == uniqueSingleton) {
                uniqueSingleton = new Singleton();
            }
            return uniqueSingleton;
        }
    }
    

    解决问题

    线程安全问题解决。

    产生问题

    但是因为使用synchronized,所以每次调用getInstance()获取对象时,都会加锁,增大无用开销。而我们只需要第一次初始化对象时加锁即可。

    三、添加双重检查锁,解决锁资源浪费问题

    public class Singleton {
        private static Singleton uniqueSingleton;
    
        private Singleton() {
        }
    
        public Singleton getInstance() {
            if (null == uniqueSingleton) {
                synchronized (Singleton.class) {
                    if (null == uniqueSingleton) {
                        uniqueSingleton = new Singleton();   // error
                    }
                }
            }
            return uniqueSingleton;
        }
    }
    

    执行逻辑:

    1. 判断对象是否已经初始化,如果是直接返回。

    2. 获取锁

    3. 再次检查变量是否初始化,如果没有,则初始化对象

    解决问题:

    每次调用getInstance()方法获取对象时,只有第一次需要加锁,后续对象初始化完成后,通过判断直接返回。减少锁资源的浪费。

    存在问题:

    实例化对象的那行代码(标记为error的那行),实际上可以分解成以下三个步骤:

    1. 分配内存空间
    2. 初始化对象
    3. 将对象指向刚分配的内存空间

    但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

    1. 分配内存空间
    2. 将对象指向刚分配的内存空间
    3. 初始化对象

    现在考虑重排序后,两个线程发生了以下调用:

    Time Thread A Thread B
    T1 检查到uniqueSingleton为空
    T2 获取锁
    T3 再次检查到uniqueSingleton为空
    T4 uniqueSingleton分配内存空间
    T5 uniqueSingleton指向内存空间
    T6 检查到uniqueSingleton不为空
    T7 访问uniqueSingleton(此时对象还未完成初始化)
    T8 初始化uniqueSingleton

    这种情况下,如果Thread B 在T7时,访问uniqueSingleton 是一个未完成的对象。

    四、添加volatile 的关键字,解决重排序问题

    使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

    public class Singleton {
        private volatile static Singleton uniqueSingleton;
    
        private Singleton() {
        }
    
        public Singleton getInstance() {
            if (null == uniqueSingleton) {
                synchronized (Singleton.class) {
                    if (null == uniqueSingleton) {
                        uniqueSingleton = new Singleton();
                    }
                }
            }
            return uniqueSingleton;
        }
    }
    

    总结

    1. 不加锁时,会出现线程安全问题
    2. 普通加锁,容易造成锁资源浪费
    3. 不加volatile,出现编译指令重排序。

    参考:https://www.cnblogs.com/xz816111/p/8470048.html

    展开全文
  • 错误的双重检查锁

    2020-12-31 20:36:26
    文章目录错误的双重检查锁正确的双重检查锁已经过时的双重检查锁参考文献 错误的双重检查锁 public static Test4 test4 = null; public static void main(String[] args) { if (test4 != null) { synchronized ...

    错误的双重检查锁

        public static Test4 test4 = null;
        public static void main(String[] args) {
            if (test4 != null) {
                synchronized (Thread.class) {
                    if (test4 != null) {
                        test4 = new Test4();
                    }
                }
            }
    

    按照期望的,就是通过外层判断减少持锁判断的次数,以增加性能.

    预期中最差的执行情况是,由于test4没有被volatile修饰,处理器缓存依旧可用,导致对于运行在其它处理器的线程来说,

    如果在test4被赋值之前就读取了变量作为缓存,那么互相的test4引用变量的修改情况不可见且为空,所有线程都需要进入synchronized持锁再判断.

    使用synchronized持有monitor lock,保证内层判断每次只有一个线程可以访问.

    这样,当一个线程赋值之后,这些持有缓存的线程再进入就会通过内层判断知道已经存在test4然后直接退出.

    但是实际情况会更糟糕,因为存在指令重排序的问题,test4 = new Test4();编译出的字节码可能存在乱序执行的情况,导致test4变量出现引用不为null但是构造方法还未执行的情况.

        public static Test4 test4 = null;
        public static void main(String[] args) {
            synchronized (Test4.class) {
                test4 = new Test4();
            }
        }
        //javap -c Test4.class
           4: monitorenter
           5: new           #2                  // class com/example/threadCoreKnowledge/ThreadStateShow/Test4
           8: dup
           9: invokespecial #3                  // Method "<init>":()V
          12: putstatic     #4                  // Field test4:Lcom/example/threadCoreKnowledge/ThreadStateShow/Test4;
          15: aload_1
          16: monitorexit
    

    可以看到test4 = new Test4();这行代码编译为字节码指令包含new,dup,invokespecial,putstatic,

    重排序后,由于test4不是volatile变量语义上没有禁止重排序,且invokespecial和putstatic之间不存在happen-before关系,也就是单线程中两个操作谁先执行都不影响后面代码的运行结果,所以执行顺序可能被打乱.

    也就存在// Method "<init>":()V的构造函数还没执行完成,
    就已经将引用赋值(putstatic)给了静态变量test4,
    这使得其它线程有可能访问到一个错误的对象(构造函数未执行/未执行完).
    往往导致程序的执行不符合预期且可能产生错误.
    

    为什么会发生重排序:
    经过如下因素处理,可以提升程序的执行速度.
    ①在编译器中生成的指令顺序,可以与源代码中的顺序不同,此外编译器还会把变量保存在寄存器而不是内存中;

    ②处理器可以采用乱序或并行等方式来执行指令;

    ③缓存可能会改变将写入变量提交到主内存的次序;

    ④保存在处理器本地缓存中的值,对于其它处理器来说是不可见的;

    如果没有使用正确的同步,这些因素都会导致一个线程无法看到变量的最新值,并且导致其它线程中的内存操作似乎在乱序执行.

    为什么已经通过synchronized确保操作有序性,操作还是会出错?

    因为synchronized的有序性是通过monitor锁的机制
    确保代码块在同一时刻只有一个线程访问

    而JVM根据Java语言规范对于"一个代码块在同一时刻只有一个线程访问"的情况,会维护一种类似串行的语义:即代码块程序执行结果与在严格串行环境中执行结果相同的情况下,上面指令重排序的因素[①②③④]就都是允许的.

    正确的双重检查锁

    这种模式避免了在域被初始化之后访问这个域时的锁定开销

        public static volatile Test4 test4 = null;
        //添加局部变量,确保变量在被初始化后,每次方法执行时只被读取一次(Test4 temp=test4)
    	//因为字段添加了volatile关键字,确保每次读写都去内存中进行,这
    	//抛弃了CPU自带的高速多级缓存,使字段可以被在其它cpu中执行的线程
    	//察觉到变化.通过减少读取的次数,更多的利用到了高速多级缓存,使程序
    	//获得硬件上的速度提升
        public static void main(String[] args) {
        	Test4 temp=test4;
            if (temp != null) {
                synchronized (Thread.class) {
                    if (test4 != null) {
                        test4 = temp = new Test4();
                    }
                }
            }
    

    双重检查锁的起因和不再推荐使用的原因

    由于早期的JVM在性能上存在一些有待优化的地方,因此延迟初始化经常被用来避免不必要的高开销操作,或者降低程序的启动时间.在编写正确的延迟初始化方法中需要使用同步.但在JDK5以及之前,同步操作是很慢的.

    DCL(double check lock)则能两全其美,在常见代码路径上的延迟初始化中不存在同步开销.

    然而,DCL这种使用方法已经被广泛地废弃了
    促使该模式出现的驱动力(同步竞争执行速度很慢,以及JVM启动速度很慢)已经不复存在,因而它不时一种高效的优化措施.

    延迟初始化站位模式能带来同样的优势,且更容易理解

    /**
     * ResourceFactory
     * <p/>
     * Lazy initialization holder class idiom
     *
     * @author Brian Goetz and Tim Peierls
     */
    @ThreadSafe
    public class ResourceFactory {
        private static class ResourceHolder {
            public static Resource resource = new Resource();
        }
    
        public static Resource getResource() {
            return ResourceFactory.ResourceHolder.resource;
        }
    
        static class Resource {
        }
    }
    //使用了一个专门的类来初始化Resource.JVM将推迟ResourceHolder的初始化操作,直到开始使用这个类才初始化,并且因为是通过一个静态初始化来初始化Resource的,JVM保证全局只在类初始化时执行一次所以不需要额外的同步操作.
    //当任何一个线程第一次调用getResource时,都会使ResourceHolder被加载并被初始化,此时静态初始化器将执行Resource的初始化操作.
    

    参考文献

    Java并发编程实战:16.1什么是内存模型,为什么需要它

    Java并发编程实战:16.2.4双重检查加锁

    Effective Java 3:Item83,慎用延迟初始化

    深入理解Java虚拟机第三版:12.3.5原子性、可见性与有序性-3.有序性

    展开全文
  • 首先给出结论:双重检查锁是需要加volatile的。 先介绍下双重检查锁
    首先给出结论:双重检查锁是需要加volatile的。
    先介绍下双重检查锁:
    

    在这里插入图片描述
    如上图,是单例设计模式中的懒汉式单例模式。其实在getInstance()方法前加上锁也能保证单例, 但直接加锁颗粒度太粗了。所以就出现了双重检查,通过第二次检查保证在多线程的情况下仍能保持单例。

    为了得出上面结论,还需要了解一下对象的创建过程,如下图:
    在这里插入图片描述

    第一条指令是在内存中开辟空间,然后对对象进行半初始化,此时m得到了一个默认的值,在第一条指令完成时,此时的m是0,而不是8。
    第二条指令是对对象进行初始化,m的值为8。
    第三条指令用于建立关联对象t与m之间的关联。

    问题的出现是因为在创建对象的时候,第二条指令和第三条指令会进行重排序。假设此时线程1通过第二次检查,在创建对象的过程中,第二条指令和第三条指令出现重排序,此时instance指向的是一个半初始化的对象。如果在这个时候线程2进来了,在执行第一次检查的时候发现instance并非为空,这时就直接返回了半初始化的instance,从而会出现问题。

    而volatile的作用是保持可见性和防止指令重排序。所以在声明instance的时候需要加上volatile关键字。

    展开全文
  • 在我们平时的开发过程中,双重检查锁的开发思路可以为我们解决一些问题,比如在解决Redis在高并发情况下可能会出现的热点缓存问题,就可以用双重检查锁的机制去解决;又比如说单例模式: @Component // 当前类也是...
  • 双重检查锁与延迟初始化 ​ 双重检查锁的错误根源? ​ 背景:在Java多线程程序中,有时我们需要采用延迟初始化来降低初始化类和创建对象的开销,双重检查锁是常见的延迟初始化技术,但是一个错误的用法。原因? ...
  • 双重检查锁模式解决了单例、性能、线程安全问题,但是这种写法同样存在问题:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。 双重检查锁模式的一般...
  • java 双重检查锁 本文讨论的问题不是新问题,但即使是经验丰富的开发人员也仍然很棘手。 单例模式是常见的编程习惯用法。 但是,当与多个线程一起使用时,必须进行某种类型的同步,以免破坏代码。 在相关文章中,...
  • 双重检查锁机制

    千次阅读 2017-06-22 23:44:45
    单例模式中用volatile和synchronized来满足双重检查锁机制 背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好...
  • 单例模式_双重检查锁

    2019-08-21 18:25:46
    package ... /** * 单例模式 * 双重检查锁 */ public class Singleton { // volatile: 禁止指令重排序 private volatile static Singleton instance; private Singleton() { ...
  • 双重检查锁来实现单例(java) 如何防止单例类有多个实例。在整个应用生命周期中,要保证只有一个单例类的实例被创建,双重检查锁(Double checked locking of Singleton)是一种实现方法。 publicclassSingleton{...
  • Java双重检查锁的错误

    2018-07-26 20:33:55
    1.错误的双重检查锁实现 public class DoubleCheckedLocking { // 1 private static Instance instance; // 2 public static Instance getInstance() { // 3 if (instance == null) { // 4:第一次检查 synchr....
  • 在整个应用生命周期中,要保证只有一个单例类的实例被创建,双重检查锁(Double checked locking of Singleton)是一种实现方法。顾名思义,在双重检查锁中,代码会检查两次单例类是否有已存在的实例,一次加锁一次...
  • 而懒汉虽然避免了这个问题,但普通的写法会在高并发环境下创建多个对象,单纯加synchronize又会明显降低并发效率,较好的两种写法是静态内部类跟双重检查锁两种。  双重检查锁这个,大家都很熟悉了,上代码: ...
  • dubbo源码中多处用到双重检查锁, 比如ExtensionLoader#getExtensionClasses 以上源码中,cachedClasses为多个线程共享资源。比如多个线程调用getExtensionClasses()方法的时候,假设X,Y线程都判断第558行classes==...
  • Java单例模式中的双重检查锁机制 public class ClassA{ volatile private static ClassA instance = null; public static ClassA getInstance() { // 双检查锁机制,避免同步导致的低并发度 if(instance ==...
  • 双重检查锁(Double-Checked Locking)被广泛用于多线程环境下实现延迟初始化实例,但是它其实是有缺陷的。
  • 在软件工程中,双重检查锁(double-checked locking[1])是一种设计模式,通过在加锁前检查锁标志(criterion),可以减少加锁的开销。只有当标志显示需要的时候才...
  • 什么是双重检查锁双重检查锁(Double-Check Locking),顾名思义,通过两次检查,并基于加锁机制,实现某个功能。 要理解什么是双重检查锁,我们从常见的单例模式说起。看第一个例子: 上图中的单例模式一看...
  • 在整个应用生命周期中,要保证只有一个单例类的实例被创建,双重检查锁(Double checked locking of Singleton)是一种实现方法。顾名思义,在双重检查锁中,代码会检查两次单例类是否有已存在的实例...
  • 双重检查锁实现单例

    万次阅读 2019-04-02 10:34:53
    public class Single { private volatile static Single single; private Single() { }; public static Single getSingle() { if (single == null) { synchronized (Single.class) ... if (sin...
  • 之前我曾经写过一篇文章《单例模式有8种写法,你知道么?》,其中提到了一种实现单例的方法-双重检查锁,最近在读并发方面的书籍,发现双重检查锁使用不当也并非绝对安全,在这里分享一下。
  • 要使Double Checked Locking双重检查锁模式正常工作,JDK必须&gt;=1.5, 并且使用volatile关键字。 http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html 这篇文章介绍了为什么Double ...
  • 很多人熟知单例模式中有一种写法是使用双重检查锁实现的,但是在网上看到的例子基本上都不正确,有些写是正确,但没有很好解析,造成很多人没有真正理解。其中,典型错误写法是这样的: public class Resource { ...
  • 双重检查锁的由来 在单例模式中,有一个DCL(双重锁)的实现方式,在Java程序中,很多时候需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象的时候才进行开始初始化。 先来看下面实现单例的方式: 非...
  • Mysql数据库一级缓存对业务双重检查锁的影响 背景介绍 业务上,会有多人可能同时对同一笔申请进行审核的情况发生,为了防止出现重复审批的情况,我首先想到了单例模式的双重锁检查。然后就套用,一直没有出现问题。...
  • Java中的双重检查锁(double checked locking) 在实现单例模式时,如果未考虑多线程的情况,就容易写出下面的错误代码: public class Singleton { private static Singleton uniqueSingleton; private ...

空空如也

1 2 3 4 5 ... 20
收藏数 630
精华内容 252
关键字:

双重检查锁