JMM初探

一个缓存不一致引发的问题

首先看下面的一个例子

// case1
public class VolatileTest1 {
 private  static int INIT_VALUE = 0;
 private static final int MAX_LIMIT = 5;
 public static void main(String[] args) {
 new Thread(()->{
 int localValue = INIT_VALUE;
 // 读不会从主存从去拿数据
 while (localValue{
 int localValue = INIT_VALUE;
 while (localValue

运行的结果是啥呢?

//"READ"线程只有第一次从主存中去读,之后变不读了,导致“READ”线程一直卡在while循环
update value to 1
the value updated to 1
update value to 2
update value to 3
update value to 4
update value to 5
// case2
public class VolatileTest2 {
 private static int INIT_VALUE = 0;
 private static final int MAX_LIMIT = 5;
 public static void main(String[] args) {
 new Thread(()->{
 int localValue = INIT_VALUE;
 // 读不会从主存从去拿数据
 while (localValue{
 int localValue = INIT_VALUE;
 while (localValue
t1 update value to 1
t1 update value to 2
t2 update value to 2
t2 update value to 3
t2 update value to 4
t2 update value to 5
t1 update value to 3
t1 update value to 4
t1 update value to 5

从中可以得到的结论:

  • 写会写入到主存(case2)

  • 读取的时候,cpu不会主动从主存中取数,而是从缓存中取数(case1)

  • 可以通过对变量INIT_VALUE增加volatile关键字,来保证内存可见性(case1),但是没保证原子性

cpu内存的简单示意图

JMM初探_第1张图片
cpu结构简图.png

从上图中可以看到,为什么会出现线程缓存不一致的情况。现在一般使用第二种cpu高速缓存协议比较多,更多可以查看《大话处理器》 一书。

线程内存示意图

对上面的图进行抽象, 得到如下。解释相关并发问题的时候可以对应来看,cpu缓存对应线程内变量。


JMM初探_第2张图片
volatile线程模型简图.png

i = i + 1发生了什么

i = i + 1 其实包含三个操作

  • 首先从主存中读取i的值,比如i = 1

  • 然后cpu运算单元计算 i = 2, 存在缓存

  • 然后cpu回写到主存\

并发编程的三个重要概念

  • 原子性

  • 可见性

  • 有序性

    有序性的例子

    // thread-1
    volatile boolean init; //通过关键字 防止2在1前 
    obj = createObj(); //1
    init = true; //2
    ​
    // thread-2
    while(!init){
     sleep(1);
    }
    ​
    doSomething();
如果thread 1的1与2语句位置调换 可能出现obj没有完全初始化的问题

happens-before

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;

  2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;

  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;

  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;

  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;

  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;

  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;

  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

java内存模型对原子性,可见性,有序性

1.原子性

对基本数据类型的变量读取和写保证原子性

a = 10 ok

b = a //no 先读取a,在进行赋值

c++ //no

c = c + 1 //no

  1. 可见性

通过volatile关键字,关键看上面的示例图

  1. 有序性

happens-before规则来保证

volatile关键字

  1. 保证重排序的是不会把后面的指令放到屏障的前面,也不会把前面的放到后面

  2. 强制对保存的修改操作立即写入主存

  3. 如果是写操作,会导致其他cpu的缓存失效

你可能感兴趣的:(JMM初探)