您的位置 首页 java

JUC(四)–Java内存模型(JMM)

什么是Java内存模型JMM?

java是跨平台的语言,但每个平台的 内存管理 是有差异的,为了屏蔽这些差异,就抽象出了一种概念JMM。

JMM主要体现在以下几个方面

  • 原子性,保证指令不会受线程上下文切换影响
  • 可见性,保证指令不会受cpu缓存影响
  • 有序性,保证指令不会受cpu指令并行优化的影响

可见性

如下所示,一个线程判断stop是否为true,不为true则一直工作,由主线程更改这个值为true来停止t线程的工作,但这样是不起作用的

 private static  Boolean  run = true;

@Test
public void testSee() throws InterruptedException {
     Thread  t = new Thread(() -> {
        while (run) {
            log.info("工作中。。。。");
        }
        log.info("停止工作");
    }).start();
    Thread.sleep(1000);
    run = false;
}
  

JUC(四)--Java内存模型(JMM)

JIT编译器会将主存中的变量复制到高速缓存中,每次取都是从这个 高速缓存 中取,即使主线程改了主内存中的值,t线程也看不到,要保证run变量的可见性,需要加volatile关键字。 jdk 1.8中可见性有所不同,测试上面代码是正常的,但最佳实践是都加上volatile关键字

JUC(四)--Java内存模型(JMM)

JUC(四)--Java内存模型(JMM)

可见性和原子性

volatile关键字只能保证一个线程修改后对其他线程可见,无法保证原子性,适用于一个线程写,多个线程读的场合,要保证原子性需要用线程安全的类如ConcurrentHashMap, Vector 或加锁。

有序性

JVM 会在不影响正确性的前提下,调整语句执行顺序。 volatile 可以保证有序性

先行发生原则(happens-before)

由于jvm为了优化会重排指令顺序,就有可能导致程序执行不正确,为了防止这种情况,指令重排需要满足happens-before原则。

happends-before的思想就是满足下面的条件,A就会优先于B发生。

happens-before总原则

  • 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,
    而且第一个操作的执行顺序排在第二个操作之前。
  • 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。
    如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

happends-before具体原则:

  1. 次序规则,一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作,按照总原则还会指令重排,但不影响结果
  2. 锁定规则,一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;
  3. volatile变量规则,对一个volatile变量的写操作先行发生于后面对这个变量的读操作,
    前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
  4. 传递规则,如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
  5. 线程启动规则,Thread对象的start()方法先行发生于此线程的每一个动作
  6. 线程中断规则,对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  7. 线程终止规则,线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。
  8. 对象终结规则,对象没有完成初始化之前,是不能调用finalized()方法的

volatile原理

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。

当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量

也就是说volatile可以保证可见性和有序性。

volatile可见性和有序性是用内存屏障实现的,分为读屏障和写屏障

加了volatile的变量在赋值后会加入写屏障,之前的变量会同步到主存中,同时写屏障之前的代码不会重排到写屏障后面,保证了有序性

 run = false;
//加入写屏障,之前的变量同步到主存  

JUC(四)--Java内存模型(JMM)

在读volatile变量前会加入读屏障,后面的数据都从主存中读取,同时能保证读屏障之后的代码不会重排到读屏障之前

 //加入读屏障,加载主存中最新数据
while (run) {
        log.info("工作中。。。。");
}  

文章来源:智云一二三科技

文章标题:JUC(四)–Java内存模型(JMM)

文章地址:https://www.zhihuclub.com/185496.shtml

关于作者: 智云科技

热门文章

网站地图