zoukankan      html  css  js  c++  java
  • 并发编程(五):设计原理

    1. happens-before

    1.1 JMM的设计要求

    设计JMM时需要考虑:

    • 程序员对内存模型的使用。希望内存模型易于理解。
      JMM的happens-before规则简单易懂,向程序员提供了足够强的内存可见性保证

    • 编译器和处理器对内存模型的实现。希望内存模型对编译器和处理器限制越少越好(便于优化)。
      JMM的基本原则:只要不该变程序(单线程和正确同步)的执行结果,编译器和处理器怎么优化都可以

    JMM的重排序策略:

    • 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序
    • 对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求,可以进行重排序

    1.2 happens-before的定义

    happens-before的定义如下:

    • 如果 A happens-before B ,那么 A 的执行结果将对 B 可见,而且 A 的执行顺序排在 B 之前
    • 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果与按happens-before关系执行的结果一致,这种重排序并不非法

    happens-before关系和as-if-serial语义是一回事:

    • as-if-serial语义保证单线程内程序执行结果不改变,happens-before关系保证正确同步的多线程程序执行结果不被改变

    • as-if-serial语义可以让程序员认为:单线程是按程序的顺序来执行的

      happens-before关系可以让程序员认为:正确同步的多线程是按先行性规则指定的顺序来执行的

    • as-if-serial和happens-before都是为了在不改变程序执行结果的情况下,尽可能提高程序的并行度

    1.3 happens-before规则(先行性规则)

    序号 规则 内容
    1 程序顺序规则 一个线程内的任意操作,先于该线程中任意后续操作
    2 锁规则 对一个锁的解锁先于之后对这个锁的加锁
    3 volatile变量规则 对一个volatile变量的写, 先于之后对该变量的读
    4 传递规则 A先于B,B先于C,则A先于C
    5 线程启动原则 Thread对象的start()方法先于对该线程的任何操作
    6 线程中断原则 线程执行interrupt操作先于获取到中断信息
    7 线程终结规则 线程的所有操作先于线程死亡
    8 对象终结规则 一个对象的初始化完成先于finalize()方法
    9 join规则 ThreadB.join(),B线程中任意操作先于B线程返回

    2. DCL双重检查锁定

    2.1 延迟初始化

    只有使用该对象(高开销)才进行初始化操作:

    public class DoubleCheckedLocking{
        private static Instance instance;
        
        public static Instance getInstance(){
            if(instance == null){						//第一次检查
                synchroinzed (DoubleCheckedLocking.class){		//加锁
                    if(instance == null){				//第二次检查
                        instance=new Instance();		//延迟初始化(有问题)
                    }
                }
            }
        }
    }
    

    大多数情况下正常初始化要优于延迟初始化。

    2.2 出现问题的原因

    初始化代码instance=new Instance();可被分解为下面三个操作:

    memory = allocate();	//1.分配内存空间
    ctorInstance(memory);	//2.初始化内存空间
    instance = memory;		//3.将instance指向内存空间
    

    操作2,3在某些编译器中会重排序,其他线程在操作3之后,操作2之前获取锁访问instance对象就会出错(未初始化)

    3. DCL问题解决方案

    两种解决方案:

    • 禁止重排(3.1)
    • 允许重排但是对其他线程不可见(3.2)

    3.1 基于volatile解决

    将instance声明为volatile型,修改为private volatile static Instance instance;就可以。

    会禁止操作2,3之间的重排序(volatile写和写之前的操作)

    基于volatile的方案处理可以对静态字段实现延迟初始化,还可以对实例字段实现延迟初始化。

    3.2 基于类初始化解决

    JVM在类的初始化阶段会执行类的初始化,在此期间,JVM会获取一个锁,同步多个线程对同一个类的初始化。

    另一种线程安全的延迟初始化方案(不使用DCL),代码如下:

    public class InstanceFactory{
        private static class InstanceHolder{
            public static Instance instance=new Instance();
        }
        public static Instance getInstance(){
        	 //初始化InstanceHolder类
            return InstanceHolder.instance;		
        }
    }
    

    两个线程并发访问getInstance示意图:(初始化锁)

    立即初始化的五种情况:

    • T是一个类,一个T类型的实例被创建
    • T是一个类,且T中声明的一个静态方法被调用
    • T中声明的一个静态字段被赋值
    • T中声明的一个静态字段被使用,而且这个字段不是一个常量字段
    • T是一个顶级类且一个断言语句嵌套在T内部执行

    3.3 类初始化处理流程

    Java的每一个类和接口C,都有一个唯一的初始化锁LC与之对应,每个线程都会至少获取一次该锁确保这个类已经被初始化。类的初始化流程分为五个阶段:

    1. 获取锁

    线程A:获取到初始化锁

    • 读取到state=initialization,将其设置为initializing

    • 释放初始化锁

    线程B:等待获取初始化锁

    2. 执行初始化

    线程B:获取到初始化锁,读取到state=initializing,释放初始化锁并进入对应的condition等待

    线程A:执行类的静态初始化和初始化类中声明的静态字段

    3. 初始化完毕

    线程A:获取初始化锁,设置state=initialized,唤醒初始化锁对应的condition中等待的所有线程

    • 释放初始化锁,A线程初始化结束

    线程B:被唤醒

    4. 结束类初始化

    线程B:获取初始化锁,读取到state=initialized,释放初始化锁,B线程初始化结束

    5. 其他线程初始化

    线程C:获取初始化锁,读取到state=initialized,释放初始化锁,C线程初始化结束

    AB线程分别获取两次初始化锁,初始化完毕后的C线程只获取一次初始化锁

    4. 内存模型总结

    4.1 处理器内存模型

    内存模型 写-读重排序 写-写重排序 读读和读写重排序 可以更早读取到其他处理器的写 可以更早读取到当前处理器的写 内存模型强度
    TSO Y Y 4(最强)
    PSO Y Y Y 3
    RMO Y Y Y Y 2
    PowerPC Y Y Y Y Y 1(最弱)

    越是追求性能的处理器模型越弱,允许越多的重排序,对处理器的束缚越少。

    JMM屏蔽了不同的处理器内存模型的差异,在不同的模拟器上为Java程序员提供了一个一致的内存模型。

    4.2 各种内存模型的关系

    JMM是语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性模型是理论参考模型。

    内存模型强度:顺序一致性模型>JMM>处理器(TSO~PPC)

    4.3 JMM内存可见性保证

    • 单线程程序:不会出现内存可见性问题

    • 正确同步的多线程程序:具有顺序一致性,JMM通过限制编译器和处理器重排序来为程序员提供内存可见性保证

    • 未同步/未正确同步的多线程程序:

      最小安全性保证:线程执行时读到的值,要么是之前线程写入的值(64位long或double可能只写入一半),要么是默认值

    4.4 JSR-133语义增强

    • volatile内存语义增强:严格限制volatile变量和普通变量之间的重排序

    • final内存语义增强:保证final引用不会从构造函数内溢出

  • 相关阅读:
    o gdb
    net -t struct ifreq
    file -x stat()-fstat()-lstat()
    I/O -x fcntl()
    I/O -x dup() dup2()
    14条---注意点
    user -x userid
    err -x perror() strerror()
    苹果电脑挂载NTFS移动硬盘
    ASP.NET Core之EF Core学习笔记
  • 原文地址:https://www.cnblogs.com/kenshine/p/14520418.html
Copyright © 2011-2022 走看看