工作内存:
每个线程的工作内存都是互相独立的,线程操作的数据只能在工作内存中操作,然后将操作完的值重新刷回主内存中,这是java内存模型定义的线程基本工作方式。
需要注意的是:jmm的内存模型与jvm的内存模型概念不一样的
上述所说的内容,可能还会有点抽象,结合一波代码配合演示图会更加好理解点
@Slf4j
public class Test01 {
private static boolean initFlag = false;
public static void refresh() {
log.info("refresh data.......");
initFlag = true;
log.info("refresh data success.......");
}
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
while (!initFlag) {
}
log.info("线程:" + Thread.currentThread().getName()
+ "当前线程嗅探到initFlag的状态的改变");
}, "threadA");
threadA.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread threadB = new Thread(() -> {
refresh();
}, "threadB");
threadB.start();
}
}
带着这个疑惑,将代码稍微改动一下,这次在代码中定义一个全局变量为count为int类型,并在A线程循环中,将变量count自增操作,再来看看它的效果如何
而我后面又在initFlag变量上加了volatile关键字,为什么能够立马感知到呢?
JMM内存模型定义
原子性:
可见性:
有序性:
如果要把一个变量从主内存中复制到工作内存中,就需要按顺序地执行read和load操作,如果把变量从工作内存中同步到主内存中,就需要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
结合上面的代码例子,通过八大原子操作,实现的流程
最后看到这,我前面似乎还漏了一个问题没有讲到,我稍微回顾一下案例场景,上面代码案例中,我定义了一个 initFlag变量,通过B线程将变量的值进行改变,发现A线程循环内部无法跳出循环的问题,后来又额外的定义了一个count变量,用于在A线程循环内部做自增的操作,然后让它进行跳出来,发现该方法并不可行,然后又继续往count值上加了一个volatile关键字,它就能够立马被A线程感知到,看到这可能还感受不到问题的存在,那么再仔细想想,结合前面的JMM内存模型的图,我在initFlag变量上加了volatile关键字,它能够被立马感知到,这是非常符合逻辑的,但是问题出现在于为什么我将关键字加在了count变量上,initFlag变量也能够被感知到呢?
这里我想回答的是,在cpu底层的缓存行中,它的每个缓存行大小为64个字节,而我们的initFlag变量它只占用了一个字节,且count变量它占用了4个字节,它们在缓存行中总共5个字节,当缓存行中的某一个变量的值发生了修改,volatile关键字会强行通知线程去拉取最新变量的值。所以这就是为什么我在count变量上加了关键字,其他线程能够及时的感知到initFlag的值发生了改变的原因。
最后我还想说明的一点是,无论我们是否加了volatile关键字,线程迟早会知道变量发生了改变,只不过区别在于关键字能够及时的通知线程变量发生了改变。
我是黎明大大,我知道我没有惊世的才华,也没有超于凡人的能力,但毕竟我还有一个不屈服,敢于选择向命运冲锋的灵魂,和一个就是伤痕累累也要义无反顾走下去的心。
如果您觉得本文对您有帮助,还请关注点赞一波,后期将不间断更新更多技术文章