JAVA高并发学习笔记(三) JMM(Java内存模型)

时间:2021-06-12 20:49:53

1.原子性

原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

 

一般的CPU的指令都是原子性的操作

 

i++ 不是原子操作

1.读取i

2.对i进行加1操作

3.写回i

JAVA高并发学习笔记(三) JMM(Java内存模型)

在多线程的操作下,可能同时执行了读操作,从而使它们得到相同的值,并都加1,结果就是多线程执行下结果是一样的。执行了2i++i的值却不是3

2.有序性

在并发时,程序的执行可能就会出现乱序

1.执行乱序

public class OrderExample {

static int a = 0 ;
static boolean flag = false;

public static void writer() {
a = 1;
flag = true;
}

public static void reader() {
if (flag) {
int i = a + 1;
System.out.println(i);
}
}
}
JAVA高并发学习笔记(三) JMM(Java内存模型)

假设:一条指令的执行是可以分为以下步骤

① 取值  IF    (将指令取出来)

② 译码和取寄存器操作数  ID    (将参数拿出来)

③ 执行或者有效地址计算  EX    (执行)

④ 存储器访问  MEM            

⑤ 写回  WB                    (写回寄存器)

这个执行是按照顺序来的 从上至下

每一步可能会使用不同的硬件

2.指令重排

一般我们认为的执行方式可能会是:

JAVA高并发学习笔记(三) JMM(Java内存模型)

假设每个环节都会消耗一个CPU时钟周期2条指令串行,会消耗10CPU时钟周期,这样太浪费时间了

第一条指令执行IF的时候第二条指令是不能执行的,因为第一条指令占用指令寄存器的时候,第二条不能使用同样的硬件,但是当第一条指令执行到ID的时候,就空出了执行IF时占用的硬件,这样第二条指令就能开始执行IF

所以指令的执行方式应该是:

JAVA高并发学习笔记(三) JMM(Java内存模型)

例如:

JAVA高并发学习笔记(三) JMM(Java内存模型)

JAVA高并发学习笔记(三) JMM(Java内存模型)

JAVA高并发学习笔记(三) JMM(Java内存模型)

因为下面2LW操作和上面的操作没有必然的联系,我们可以将指令进行重排

JAVA高并发学习笔记(三) JMM(Java内存模型)

本来是14个时钟周期执行完成的经过重排之后变成了12个时钟周期

 

指令重排的原则不能破坏串行语义的执行

指令重排是优化代码的一种方式

优化的结果就是一个线程去看另外线程的执行顺序可能会出现乱序的现象,破坏多线程的语义

3.可见性

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改

可能由各个环节优化产生,没有办法从一个线程当中看另外一个线程一个变量执行到什么程度去推测另外一个变量的情况。

 

编译器优化:

一个编译程序,在编译代码的时候,一个线程中的变量的值优化到了寄存器中,另外一个线程将这个变量放入了高速缓存cache中,这时候这2个线程未必能在同一时间发现对方修改了这个变量,毕竟在多核CPU每个CPU都有自己的一套寄存器,自己的一套cache,每个变量可能被不同线程的寄存器或cache缓存住,所以不能保准它们之间一定是一致的。

JAVA高并发学习笔记(三) JMM(Java内存模型)

硬件优化

比如CPU想把数据写入内存中去,可能并不是直接把数据写入内存中,因为这样会很慢。为了优化,会有一个硬件队列,先把数据写入硬件队列,通过批操作,把数据批量写入内存,如果对同一个内存地址做了多次不同的读写,认为这是不必要的,因为一定是最后一次读写为准,对前面几次的内存地址读写就不操作了不放入队列中,而对最后结果写入内存。导致之前几次对内存的读写其他线程是看不到的。

 

Java虚拟机层面的可见性

 

来源:

http://hushi55.github.io/2015/01/05/volatile-assembly

public class VisibilityTest extends Thread {

private boolean stop;

@Override
public void run() {
int i = 0;
while (!stop) {
i++;
}
System.out.println("finish loop, i = " + i);
}

public void stopIt() {
stop = true;
}

public boolean getStop() {
return stop;
}

public static void main(String[] args) throws InterruptedException {

VisibilityTest v = new VisibilityTest();
v.start();

Thread.sleep(1000);
v.stopIt();
Thread.sleep(2000);

System.out.println("finish main");
System.out.println(v.getStop());

}

}

-server模式运行上述代码,永远不会停止

运行线程的汇编代码:

JAVA高并发学习笔记(三) JMM(Java内存模型)

java虚拟机优化后导致这种现象。可以在声明stop变量的时候加个volatile关键字避免这种问题

 

可见性问题的成因是比较复杂的,可能由各个层面上的优化产生的。可见性问题就是在一个线程中看不到另外一个线程对某个变量的修改


4.Happend-Before

程序顺序原则:一个线程内保证语义的串行性

l n volatile规则volatile变量的写,先发生于读,这保证了volatile变量的可见性

l n 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

l n 传递性A先于BB先于C,那么A必然先于C

l n 线程的start()方法先于它的每一个动作

l n 线程的所有操作先于线程的终结(Thread.join()

l n 线程的中断(interrupt())先于被中断线程的代码

l n 对象的构造函数执行结束先于finalize()方法