多线程(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   ------

 

 

posted @ 2020-05-16 22:55  haohao1234  阅读(183)  评论(0编辑  收藏  举报