zoukankan      html  css  js  c++  java
  • JMM总结

    一个东西你自己知道,跟你能说出来是两回事。
    不但能说出来还要让别人能够听明白又是一件难事。
    所以要自己写一遍看看到底问题出在哪里,能不能把这个问题说明白。
    遣词造句见功底

    JMM java虚拟机内存模型

    虚拟机内存模型讲到了如下几点:
    1.硬件与缓存一致性协议
    2.工作内存与主内存
    3.内存间的交互操作
    4.volatile的特殊规则
    5.long,double的特殊规则
    6.原子性、可见性、有序性
    7.先行发生原则

    一、硬件与缓存一致性协议
    讲到java虚拟机内存模型首先扯到了PC的内存模型。PC内存模型干嘛的,简单说就是压榨CPU效率,让CPU不要闲下来。

    PC内存模型主要有两点
    1是解决cpu和内存速度差异,为了压榨cpu
    2是提高cpu运算单元执行效率,也是为了压榨cpu

    对于第一点呢,由于cpu运行速度快,内存运行速度慢,两者速度差别太大,引入了高速缓存。对于多核cpu,每个核都有一个高速缓存,这里就出现了多个高速缓存共同访问内存同一个变量引发的数据一致性问题。
    为了解决数据一致性问题引出了缓存一致性协议。
    对于第二点呢,引出了cpu的乱序执行。所谓乱序执行就是把指令分发到各个处理单元,让原本顺序执行的代码通过一定的优化最大化的将各个处理单元利用起来。
    这个一定的优化前提是可以得到正确的结果,正确结果的前提又是什么呢,是数据依赖关系,没有依赖关系的可以随便排,有依赖关系的不能随便排。
    比如A = 1; B = 2; C = A; B的赋值操作就可能发生在A=1之前,C的赋值操作就一定要发生在A=1之后,不然结果就是错的。这三条语句,B的赋值操作可以乱排,A和C的赋值操作就不能乱序执行。

    二、工作内存与主内存
    java虚拟机的内存模型可以类比pc的内存模型。

    虚拟机的内存模型主要目的是定义共享变量访问规则,什么是共享变量访问规则,就是一个变量如何从内存中读取出来,又如何写入到内存。这里的变量不同于java语言的变量,指的是实例字段,静态字段和构成数组成员的元素。
    局部变量和方法参数属于线程私有不存在共享一说,所以不需要考虑。

    类比pc内存模型第一点,java虚拟机引入了工作内存和主内存。
    什么是工作内存,什么是主内存:
    工作内存指的就是线程私有,主内存就是公共区域,线程都可以访问,共享变量都在主内存中,线程启动时会从主内存中拷贝一个共享变量的副本到工作内存。

    三、内存间的交互操作
    交互操作有8个原子操作分别是
    lock,unlock,read,load,use,assign,store,write
    这里没什么好说的,《深入理解java虚拟机》讲到都很清楚

    四、volatile的特殊规则
    两个特殊规则
    1是可见性
    2是禁止指令重排序

    volatile说起来很简单
    首先线程修改了变量要立即同步到主内存,工作内存要访问volatile修饰的变量要先从主内存中更新过来。
    这个操作就确保了可见性。

    那是不是有了可见性,volatile修饰的变量就一定是线程安全的呢,答案是不一定,为什么,简单说就是不具备原子性。
    书里举的例子race++,这个++操作编译成字节码要四个字节码,分别是getstatic,iconst_1,iadd ,线程A和B都执行了getstatic,后面add操作之后就可能把较小的值写入主存了。

    volatile禁止指令重排序
    要说禁止,那肯定是有点问题的,什么问题呢,简单说就是多线程条件下指令重排序会让代码返回错误的结果。
    书里举的例子是一个boolean类型变量config,线程A执行设置配置文件的任务,由于指令重排序就有可能配置文件操作还没执行,config就赋值给了true,导致线程B开始执行操作了。这时候就出错了。
    所以config要用volatile来修饰。来禁止指令重排序,确保config赋值之前,配置文件操作能够执行完毕。

    那怎么确保的呢
    是通过lock前缀,外加一个add操作,lock前缀的语义是跟cpu指令集直接相关的,这个指令的骚操作就是把缓存里的值更新到主存,同时呢让别的cpu缓存里的变量值无效化。
    这个骚操作实现是基于“嗅探”机制。也就是说缓存不止在与总线进行数据交互时发生关系,而是不停嗅探总线上发生的数据交换,同理别的缓存写了,我这个cpu也能得到通知,以此来确定自己的缓存是不是要无效化。
    这里也不要忽略add操作,正是add操作把数据更新到主存,所以这个add也不是白给的。

    指令重排序不是指令乱排序,需要根据数据之间的依赖关系来决定如何重排序优化。lock前缀形成的内存屏障意思就是别的指令不能跑到lock前缀之后再执行。也就是说lock指令执行的时候,你代码的所有指令都应该执行完毕了。
    也就是说config赋值的时候,配置文件应该执行完毕了。

    五、long,double的特殊规则
    我从书里理解就是没有特殊规则

    六、原子性,可见性,有序性
    原子性不可分割,除了内存间6个交互操作read,load,use,assign,store,write之外。 还提供了monitorenter和monitorexit对应java语言就是synchronized关键字。
    可见性三个,volatile,synchronized,final
    volatile可见性体现在先从主存更新,完了要同步主存
    synchronized可见性体现在unlock操作要把变量值更新到主存,lock操作是要从主存更新
    final体现在除了this逃逸之外,对象创建完毕对所有线程皆可见
    有序性就是同一线程来看是按控制流执行,对不同线程来看是乱序执行。
    有序性两个volatile和synchronized
    volatile体现在禁止指令重排序
    synchronized体现在同一时刻只能有一个线程对变量进行访问。

    7、先行发生原则
    这个说的什么呢,讲的是程序不可能都靠volatile和synchronized来确定有序性。那么一般是如何确定有序性有如下几个规则
    Program Order Rule : 这个叫程序次序规则。什么意思呢,就是程序按照控制流顺序执行,这里要注意是控制流不是程序代码,所谓控制流比如for循环,if else之类的。
    Monitor Lock Rule: 这个叫管程规则,不知道怎么翻译的,还不如看英文来的实际。这个说的是synchronized,unlock一定要发生在lock之前
    Volatile variable Rule: volatile规则,还是老生常谈的一个变量同步到内存先行发生于从内存读取
    紧接着是三个Thread相关的
    Thread start Rule: 简单说就是执行start方法,线程才算开启
    Thread interrupted Rule: 讲的是线程中断,这个实在是太绕口了。对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
    Thread termination Rule: 讲的是线程终止,一个线程的所有操作都先行发生于线程的终止,怎么看线程是否终止,我们可以通过Thread.join()方法结束、 Thread.isAlive()的返回值等手段检测到线程已经终止执行
    接着讲了一个方法相关的
    Finalizer Rule: 对象的创建先行发生与执行finalizer()方法
    最后讲的是A大于B,B大于C的问题
    Transitivity:操作A现行发生于B,操作B先行发生于C,那么A就先行发生于C

    先行发生原则,有什么用,可以通过这些规则来对程序的有序性进行大致的判断。

  • 相关阅读:
    多选择文件打开对话框
    DirectoryExists
    获取IP地址
    获取WINDOWS特殊文件夹
    WPF WebBrowser
    DELPHI TDownLoadURL下载网络文件
    同步窗体移动 FormMove
    FireMonkey 使用Webbrowser
    网页截取图片
    FormMove
  • 原文地址:https://www.cnblogs.com/cfdroid/p/14367068.html
Copyright © 2011-2022 走看看