当前位置: 首页 > 网络应用技术

让我们一起学习并发编程:Java内存模型(8)双检查锁和延迟初始化

时间:2023-03-08 16:30:46 网络应用技术

  在Java多线程中,有时可能会使用延迟的初始化来减少初始化和创建对象的费用。双检查锁(通常在一个示例中在饥饿的人中使用)是一个常见的延迟初始化方案,但用法是错误的用法本文将分析双检查锁的错误根部以及两个线程的延迟初始化方案。

  在Java程序中,有时可能有必要延迟一些高成本对象的初始化,并且仅在使用这些对象时才能执行初始化。这次,程序员可能会使用延迟初始化。延迟线程安全性,以避免不必要的问题。

  非线程安全延迟初始化代码的示例:

  在UnsafaimazyInitialion类中,假设线程A执行1执行2时执行1,则线程A可能会看到实例对象未初始化(laster -up将讨论问题的根本原因)。

  同步处理解决方案:

  getInstance()方法的同步处理,同步将带来性能开销。在getInstance()呼叫的情况下,可以接收此解决方案,但是如果经常调用getInstance(),则该程序的整体性能将减少。(尤其是(尤其是)。在JVM早期,没有锁定策略)。

  双检查 - 锁解决方案:

  如果上述代码,则该实例第一次不是无效的,则您不需要长时间执行锁定和初始化工作,这可以大大减少同步带来的性能开销,但也存在问题使用double Check -In锁定,当实例未正确初始化时,可以确定实例== NULL的代码。此问题的原因是重新传动说明。以下将详细描述。您也可以阅读我以前的文章!所以这是一个错误的不完美解决方案。

  2.1实例分析=新实例();实例= new实例();这条代码行可以理解为三个伪伪的伪代码(JVM中的指令):

  可以对上述代码2和3进行排序(某些JIT编译器为真)。

  由于上述重型分类,必须遵守Java程序时必须遵守的线内语义。该测序尚未更改单个线程中的程序执行结果,如果重新排序可以带来性能优化,则它是Java语言规范“ Java”语言规范允许的允许。

  2.2分析系统中单个线程中的线程内性别=新实例();

  线程执行的序列图

  实例= new实例()在多线程线程中;可能存在的可能存在的执行顺序图表:

  多线程执行序列图

  由于必须将单个线程中的线程语义内部语义符合到单个线程中的线Intra-thread语义,因此线程A的执行结果不会更改;但是,在上面的多线程执行中,线程B可能会读取未正确完成的实例对象。

  返回到doubleCheckedlocking的示例代码,当第一个实例== null判断为真时,线程B可能是正确的,并且线程B随后将对象访问到实例引用,但是此对象目前尚未初始化。

  多线程执行时间表:

  T1A1:分配对象的内存空间T2A3:将实例ding设置为内存空间t3b1:确定实例是否为nullt4b2:由于实例不是null,thread B将访问实例T5A2引用的对象T5A2:初始化对象T6A4:T6A4:对象TOSCENS.TOSSISS.KEY键。点包括上述定时图表和解释。不难发现问题是在实例化对象时实例化对象的指令分类,因此对象被“更新”,因此我们有以下两个解决方案:

  以下描述了特定的实施计划。

  只需对DoubleCheckedLocking进行小修改(需要基于JDK1.5及以上)

  当实例是挥发性参考变量时,将禁止2和3的重分排序。序言如下:

  多线程执行序列图

  该方案是通过禁止进行重量分类来实现的。

  JVM的初始化阶段(即加载类并使用线程之后),执行了类的初始化。在执行类的初始化中,JVM将获得锁定。此锁可以同步初始化初始化通过多个线程的同一类。

  此功能的实现称为(按需hostiom初始化)。

  示例代码:

  假设该线程A和线程B同时执行GetInstance()方法。以下是一个示意图:

  该方案本质上是一个沉重的排序,但是不允许非结构线程B看到实例化完成的不可言喻的对象,并且使用了JVM类初始化的特征。

  初始化类包括此类中此类声明的静态字段的静态初始化和初始化。

  那么,什么时候可以初始化课程?在Java语言规范中,在以下情况下发生任何一种情况,类型T类或接口T类型T将立即初始化:

  在instanceFactory示例代码中,静态字段实例在Instanceholder中使用,这会导致InstanceHolder触发Instanceholder对象的初始化,从而初始化实例对象。

  在执行Java代码期间,将同时初始化类或接口。因此,在Java语言规范中,将需要特定的JVM来同时处理此过程。(实施规范是与每个类或接口相对应的唯一初始化锁定LC。从C到LC的映射由JVM实现)。

  让我们看一下“ Java并发编程艺术”的作者如何通过5个步骤解释此过程。

  5.1在第一阶段,类或接口的初始化是通过同步类对象来控制的(类对象的初始化锁定)。该线程获得锁定的线程将始终等待,因为知道当前线程可以获得当前线程的初始化锁此类对象。

  假设线程A和线程B最初初始化未初始化的类对象(目前标记为状态= Noinitialization),图标如下:

  类初始化优先阶段

  类初始化 - 执行表的第一阶段:

  T1A1:尝试获取类对象的初始化锁。在此,假设线程A获取初始化锁。B1:尝试获取类对象的初始化锁。由于线程A获取锁,因此线程B正在等待初始化锁定T2A2:线程A看到对象未初始化(状态= Noinitialization),螺纹设置state = Noinitializationt3a3:线程A版本A版本初始化初始化locitization Locitization 5.2第二级线程A第二阶段线程A执行A类的初始化,线程B正在等待初始锁的相应条件。

  图标如下:

  类初始化阶段2

  分类初始化 - 第二阶段执行时间顺序表:

  T1A1:静态字段在执行类B1中声明为静态和初始化:获取初始化锁定T2B2:读取状态= initializedt3b3:preightional lock t4b4:等待初始化锁中的第一个阶段线程。所有等待条件的线程

  类初始化阶段3

  类初始化 - 第三阶段执行时间顺序表:

  T1A1:获取初始化锁定T2A2:SET state = initializedt3a3:唤醒所有线程T4A4等待条件:释放初始化锁定T5A5:线程A的初始化过程A完成了第四级线程B END B类B的初始化处理。

  类初始化阶段4

  类初始化第三阶段执行时间顺序表:

  T1B1:获取初始化锁定T2B2:读取状态= initializedt3b3:发布初始化锁的初始化t4b4:线程B类的初始化过程完成第五阶段线程C执行类的初始化过程

  类初始化阶段5

  类初始化第五阶段执行时间顺序表:

  T1C1:获取初始化锁定T2C2:读取状态= initializedt3c3:释放初始化锁定的初始化T4C4:线程B类的初始化过程已完成。由于C类的初始化在第三阶段完成,因此线程C执行类的初始化过程相对简单。

  通过比较基于挥发性和基于类的初始化方案的双重锁定方案,发现通过使用类初始化解决方案实现的代码更加简洁。但是,基于波动性的双重检查方案是一个附加优势是它不仅可以实现静态字段的延迟初始化,而且还可以延迟实例字段的初始化(因为JVM类初始化只能初始化静态字段)。字段延迟延迟初始化减少了初始化类带来的开销和创建实例,但它还增加了访客延迟初始化字段的开销。实际开发中的正常初始化比延迟初始化要好。

  如果您一定要延迟初始化,那么如何选择?

  本文总结了“ Java并行编程艺术”。Java内存模型的摘要已经完全结束,这需要很多夜晚。尽管本文的知识点来自书籍,但作者也从事以下工作:

  代码单词并不容易,请注意更多。