zoukankan      html  css  js  c++  java
  • JMM基础

    参考书:《Java并发编程艺术》
    附图均来自:《Java并发编程艺术》

    先了解一下基础的概念:什么是JMM?

    JMM(全称:Java Memory Model), JMM定义的是线程和主内存间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。(有个印象,下面具体介绍)

    在学习JMM之前,其实要先保留两个问题。1.那些数据需要JMM控制?2.JMM是怎么进行干预线程的操作的?

    源代码到可执行的变成指令序列(重排序)

    首先,我们要知道的是,我们所写的代码,并不是原封不动的被转换成可执行的序列。为了提高性能,编译器和处理器经常会进行 重排序

    编译重排序的过程

    JMM的编译器会对特定类型的编译器进行限制。 所以,首先要了解编译器的重排序处理。

    不同处理器的编译器有所不同,但是都遵循以下规则:

    1.数据的依赖性

    含义:当两个操作访问一个变量,且两个操作中一个是写操作,这两个操作存在数据的依赖性。

    这两个操作,可能是:1.先读后写;2.先写后写;3.先写后读

    一般存在数据依赖性的数据操作,编译器和处理器不会重新排序两个操作的执行顺序(只针对单处理器);

    2.as_if_serial语义

    含义:不管怎么重排序,单线程的执行结果不能被改变。因此不会对存在数据以来的操作进行重排序;

    上面的这些,我们可以看出,在单线程的情况下,根据数据的依赖性,数据执行结果不能被发生改变。

    但是我们实际开发中经常会遇到多线程的情况,如下:

    class Test{
       int a = 0 ;
       boolean flag = false;
    
       public void wirte(){
         	a ++ ;
    	flag = true;	
       }
       
       public int read(){
    	if(flag){
                return ++a;		
    	}
       }	
    }
    

    上面的这个类,例如有两个线程同时执行write方法和read方法,因为就单个方法来所编译器的出现重排符合数据依赖性,但是实际多线程访问会出现数据异常查询和写入的问题。

    像上面的这种多线程操作同一个变量,且读和写操作不在同一个线程中时,就要通过JMM进行编译器约束了。

    了解下JMM的原则

    在开始了解JMM编译重排序之前,我们先了解一个模型:顺序一致性模型;

    顺序一致性模型

    顺序一致性内存模型是一个理论参考模型,处理器内存模型和编程内存模型以顺序一致性为参考。

    顺序一致性特征:

    1.一个线程的所有操作,必须按照程序的顺序来执行;

    2.所有的线程都只能看到单一的操作执行顺序。在顺序一致性模型中,每个操作必须原子执行且立刻对所有线程可见。

    happens-before(JMM顺序执行的原则)

    happens-before是JMM最核心的概念,也是JMM顺序执行的要遵循的原则。

    定义:

    1.一个操作happens-before第二个操作,那么第一个操作执行顺序在第二个操作之前,并且第一个执行结果对第二个操作可见。

    2.只描述两个操作的关系,java平台不一定按照这种关系执行。

    happens-before的几种规则

    1、程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。

    2、监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。

    3、volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。

    4、传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

    JMM控制

    现在可以回答上面预留的两个问题的问题了。

    1.那些数据需要JMM控制?

    • 方法区的实例,域都是线程共享的;这些是不会涉及到重排序的问题,所以不是JMM管理的范围

    • 局部变量的线程是私有的,才涉及到线程之间的通信;

    2.JMM是怎么进行干预线程的操作的?

    这就是我们接下来要了解的知识,内存屏障(barriers)和临界区

    然鹅JMM并不是禁止所有重排序,对于不会改变程序运行结果的重排序,JMM不做要求;

    内存屏障(阻止编译器重排序)

    内存屏障的类型

    • volatile变量就是通过屏障阻止编译重排序的;

    • final修饰的变量也是通过屏障进行编译重排序的;

    volatile变量

    1.volatile重排序的规则表

    2.举个栗子(来自《Java并发编程艺术》)

    class VolatileBarrierExample {
           int a;
           volatile int v1 = 1;
           volatile int v2 = 2;
           void readAndWrite() {
               int i = v1;
    }
    

    临界区(临界区内可以重排序,但是临界区内的代码不能“逸出”到临界区外)

    显式的通过给代码增加锁,创建临界区;当线程释放锁的时候,JMM会把本地内存中的数据刷新到主内存中。

    指令执行中,是通过#Lock前缀指令,指令在执行期间会锁住。

    先看一个代码

    class Test{
       int a = 0 ;
       boolean flag = false;
    
       public synchronized void wirte(){
         	a ++ ;
    	flag = true;	
       }
       
       public synchronized int read(){
    	if(flag){
                return ++a;		
    	}
       }	
    }
    
    
  • 相关阅读:
    有什么样的博客手机客户端
    v2ex thread record
    Screengrab! firefox截图插件
    现在看一次咳嗽感冒就差不多要1k了,恐怖
    海归人才网
    LinkedIn公司实现的实时搜索引擎Zoie
    xipian的php中文分词
    海归人才网
    idea
    snagit number stampsgood for ppt
  • 原文地址:https://www.cnblogs.com/perferect/p/13680158.html
Copyright © 2011-2022 走看看