zoukankan      html  css  js  c++  java
  • 多线程(8)JMM底层原理

    一。JVM内存结构

    JVM运行时的数据区

     

    分为五部分

     堆: 运行区域最大的一块,占用内存最多。new的实例对象,数组。优势:运行时动态分配

    JAVA栈:保存各个基本类型,对象引用。编译时确定大小

    方法区:已经加载的static静态变量或类信息或常量信息,永久引用例如static People p = new People();的p引用

    本地方法栈:native方法相关栈

    程序计数器: 当前线程执行的字节码的行号数,上下文切换时会被保存下来还包括下一条需要执行的指令 

    参考JVM虚拟机知识: http://liuwangshu.cn/java/jvm/1-runtime-data-area.html

    二。JAVA对象模型

    JAVA对象本身的存储模型

     

     

     三部分

    方法区:JVM会给这个类创建个类信息放在这

    堆: new创建实例对象时,会在堆放每个实例包括对象头和实例数据

    栈: 方法中的参数变量,当某个对象被方法调用了,就会在栈中保存对象的引用,

    三。JAVA内存模型----JMM

    1.什么是JMM(JAVA内存模型)

    JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),
    本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

    2.为什么需要JMM JAVA内存模型
    JMM是一种规范,保证执行的一定顺序:重排序,可见性,原子性 
    先看下java到最后执行的CPU指令的流程
    • 最开始我们编写的是*.java 文件
    • 执行javac 编译成java字节码文件(*.class文件)
    • JVM会执行字节码文件,并把字节码文件转化成机器指令
    • 机器指令会在CPU上运行,也就是最终的程序执行

    但是不同处理器的机器指令不相同,执行顺序不一样。所以不同处理器执行结果不一致,需要统一的标准即JMM保证多线程运行结果可期。 

    2.1 重排序:线程内部执行顺序与JAVA代码中的书写顺序不一致,可能被颠倒

    重排序的三个场景

    (1)编译器优化:提高执行效率,例如将对数据a的操作放在一起执行,避免反复读取的消耗

    (2)CPU优化: 和编译器优化类似

    (3)内存的重排序: 实际上是因为可见性问题造成的类似重排序的现象,线程1操作了a的值但没有写到主内存,所以线程2拿到还是原来的值

    2.2 可见性:Cpu有多级缓存,每个线程都有本地内存,读写会将修改数据先保存在本地内存里,再放入主内存。

                         主内存是多线程共享的,线程间通信需要通过主内存来中转

    线程1修改了a,b, 线程2可可能看不到a的修改甚至看到部分修改例如x =a, y = b这里x可能看到,y看不到

    三.Happens-before原则

    产生原因:因为JVM会对代码编译优化,重排序,所以需要Happens-before原则规定一些禁止编译优化的场景,保证并发的正确性

    解释:如果操作1 happens-before操作2,那么操作1的操作结果对于操作2是可见的

    a. 单线程原则

    a=3;
    b=a;

    在单线程中,a=3在b=a之前,那么a=3将在b=a之前执行,且a对b可见,结果是b=3。

    因为同一线程都使用本地内存,是可见的。但是它不影响重排序,只要后面的操作能看见前面的操作就行

    b. 锁操作(synchronized和lock)

     同一个锁上的两个线程,后执行的线程能看到先执行的线程所有操作。

    c.volatile变量

    volatile变量只要写入了,后面的操作都能看到,禁止重排序

    d。 线程join

    thread1.join(); 执行后面的主线程语句都能看到thread1的执行结果

    c. 传递性

    操作1 先于操作2, 操作2先于操作3,那么操作1 先于操作3

    d. 中断

    一个线程被其他线程interrupt,那么检测中断(isInterrupted)一定能看到

    e. 构造方法

    finallize方法一定能看到构造方法的操作结果

    f. 工具类的HB原则

    (1)线程安全的容器例如ConcurrentHashMap 的get方法一定能看到在他之前执行的put方法,即使是不同线程

    (2)CountDownLatch 的await一定能看到其他线程执行的countDown结果

    (3)Semphore的aquire已经能看到其他线程释放

    (4)Future 的get方法能拿到完整结果

    (5)线程池提交很多任务,每个任务都能看到之前提交的任务结果

    (6)CyclicBarrier

    重点关注锁操作和volatile,这是需要我们写代码的

    public class VolatileTest {
        public static void main(String[] args) {
            while (true){
                Vis vis = new Vis();
                Thread t1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        vis.setVal();
                    }
                });
                Thread t2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        vis.print();
                    }
                });
                t1.start();
                t2.start();
            }
        }
    }
    class Vis{
        int a=0;
        int b=0;
        public void setVal(){
            a=3;
            b=a;
        }
    
        public void print(){
            System.out.println("a:"+a+",b:"+b);
        }
    }

    这里的结果:

    a:3,b:3
    a:0,b:0
    a:0,b:3   ------t2只看到t1部分结果,可见性
    a:3,b:0   ------

  • 相关阅读:
    C#批量附加指定目录下的所有数据库文件到数据库中
    UDP聊天代码发送消息客户端代码
    IE6 中 a:hover 的bug
    HTML 里的 if 条件用法
    How to Implement Ajax in WordPress Themes
    如何顺利的通过高级职称评审(转)
    WEB前端工程师如何做职业规划?(转)
    去掉IE浏览器里的脚本控件提示
    网页的横向滚动条
    Top 10 CSS Table Designs(转)
  • 原文地址:https://www.cnblogs.com/t96fxi/p/12902682.html
Copyright © 2011-2022 走看看