java violate 优化_高性能编程之java的volatile关键字
发布日期:2021-06-24 16:33:35 浏览次数:3 分类:技术文章

本文共 3423 字,大约阅读时间需要 11 分钟。

1. volatile语义解决的问题:

(1)可见性的保证:如果一个线程线程对共享变量进行修改,能够实现本地的内存的立即回写到主内存,通过嗅探机制,其他线程能够立即感知到最新的变化。

(2)顺序性的保证:禁止JVM或者CPU对进行指令重排。

可见性保证举例:

//线程A

boolean running= true;

public void run(){

while(running){

System.out.println(“hello!”);

}

}

//线程B

public void setRunStatus(boolean running){

this. running = running;

}

上面的代码,如果调用setRunStatus,把running更改为false,在单线程的环境中是能够正常中断的,但是如果在多线程的环境里面,上面代码或许可以中断,但是也有可能会导致无法中断线程,从而引起死循环。这是因为每个线程运行的时候都有自己的工作内存,那么线程A在运行的时候,会将running变量的值拷贝一份放在自己的工作内存当中。如果线程B修改改了stop变量的值之后,但是还没来得及写入主存当中,线程A被挂起(CPU的调度机制是时间片轮转机制),那么线程A由于不知道线程B对running变量的修改,还会一直循环下去。

可以使用running前面加上volatile来解决该问题:

(1)使用volatile关键字之后,变量如果被修改会被强制将修改的值立即写入主存;

(2)使用volatile关键字之后,当线程B进行修改时,会导致线程A的工作内存中缓存变量running的缓存行失效;

(3) 由于线程A的工作内存中缓存变量running的缓存行失效,所以线程A再次读取变量running的值时会去主存中读取。当在线程B修改running的值时,也会让线程A的工作内存中的变量running的缓存行无效,然后线程A读取该变量时,发现缓存行无效,它会去对应的主存读取最新的值,然后更新到自己的工作内存。那么线程A读取到的就是最新的正确的值。

顺序性保证举例:

为了提高性能,JVM编译优化和CPU 都可能对程序进行指令重排,只要重排的指令语义保持一致。指令重排序可能会给我们的程序执行带来不确定性,比如:

public class OrderExample{

private boolean running= false;

private String property;

public void init() {

property = getProperty();//1

running = true;//2

}

public void work() {

if (running) {//3

System.out.println(property+” ok”);//4

}

}

public String getProperty(){

...

}

}

running变量是为了作为是否执行的判断条件。其中1,2操作是没有数据依赖关系,同样3、4操作也是没有数据依赖关系。那么CPU可能对1、2和3、4操作都可能进行重排序。现在开启线程A操作init()方法,线程B操作work ()方法,重排序将会对多线程产生影响。

2操作有可能被排序在1操作前面执行。当CPU时间片转到线程B。线程B判断 if (running)为中的running是否为true,接下来接着执行操作4,但property可能还没有初始化,从而导致4发生异常。为了防止重排序引发的问题。Java内存模型规定了使用volatile来修饰相应变量时,可以让CPU在处理指令的时候禁止重排序。

public class OrderExample {

private volatile boolean running = false;

private String property;

public void init() {

property = getProperty();//1

running = true;//2

}

public void doSomething() {

if (running) {//3

System.out.println(property+” ok”);//4

}

}

public String getProperty(){

...

}

}

java内存模型Happen-before 机制与volatile的约束:

Java的volatile 关键字在可见性的基础上提供了 happens-before担保机制。happens-before 机制确保了 volatile 的完全可见性:

(1)、如果其他变量的读写操作原本发生在 volatile 变量写操作之前,他们不能被指令重排到 volatile 变量的写操作之后。发生在 volatile 变量写操作之后的读写操作仍然可以被指令重排到 volatile 变量写操作之前。happen-after 重排到 (volatile写) 之前是允许的,但 happen-before 重排到之后是不允许的。

(2)、如果其他变量的读写操作原本发生在 volatile 变量读操作之后,他们不能被指令重排到 volatile 变量的读操作之前。发生在 volatile 变量读操作之前的读操作仍然可以被指令重排到 volatile 变量读操作之后。happen-before 重排到 (volatil读) 之后是允许的,但 happen-after 重排到之前是不允许的。

volatile不能保证原子性:

i = 5; //1

i++; //2

i= i + 1; //3

以上三条操作中,只有1操作是原子操作,这是因为:

1操作是将数值1赋值给i,也就是说线程执行这个语句的会直接将数值5写入到线程的工作内存中。

2操作实际上包含2个操作,它先要去读取i的值,然后将i的值写入工作内存,虽然读取i的值和将i的值写入工作内存都是原子性操作,但是两个操作合并起来就是不是原子操作。

同样的,i++和 i = i+1包括3个操作:先读取i的值,然后进行加1操作,写入新的值。

2、volatile的实现机制

在Java中对于volatile修饰的变量,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序问题

编译器在指令序列插入的内存屏障保守插入机制如下:

在每个volatile写操作的前面插入一个storestore屏障。

在每个volatile写操作的后面插入一个storeload屏障。

在每个volatile读操作的后面插入一个loadload屏障。

在每个volatile读操作的后面插入一个loadstore屏障

· storestore屏障:对于语句store1; storestore; store2,在store2及后续写入操作执行前,保证store1的写入操作对其它处理器可见。(也就是说如果出现storestore屏障,那么store1指令一定会在store2之前执行,CPU不会store1与store2进行重排序)

· storeload屏障:对于语句store1; storeload; load2,在load2及后续所有读取操作执行前,保证store1的写入对所有处理器可见。(也就是说如果出现storeload屏障,那么store1指令一定会在load2之前执行,CPU不会对store1与load2进行重排序)

· loadload屏障:对于语句load1; loadload; load2,在load2及后续读取操作要读取的数据被访问前,保证load1要读取的数据被读取完毕。(也就是说,如果出现loadload屏障,那么load1指令一定会在load2之前执行,CPU不会对load1与load2进行重排序)

· loadstore屏障:对于语句load1; loadstore; store2,在store2及后续写入操作被执行前,保证load1要读取的数据被读取完毕。(也就是说,如果出现loadstore屏障,那么load1指令一定会在store2之前执行,CPU不会对load1与store2进行重排序)

转载地址:https://blog.csdn.net/weixin_33865450/article/details/114592972 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Java中如何导入DW当中_用Dreamweaver插入Java特效方法
下一篇:百一测评java基础答案_百一测评——java基础笔试题及详解

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月13日 02时49分01秒