java 线程volatile_Java 多线程经典面试题volatile

问题

当多个线程并发同时进行set、get时,其它线程能否感知到flag的变化

public class ThreadSafeCache {

boolean flag = true;//默认设置true

public boolean isFlag() {

return flag;

}

public synchronized ThreadSafeCache setFlag(boolean flag) {

this.flag = flag;

return this;

}

public static void main(String[] args) {

ThreadSafeCache threadSafeCache = new ThreadSafeCache();

//循环创建多个线程

for (int i = 0;i < 10;i++){

new Thread(() -> {

int j = 0;

while(threadSafeCache.isFlag()){

j++;

}

System.out.println(j);

}).start();

}

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

threadSafeCache.setFlag(false);

}

}

运行结果

3LGLLMKHE9J9HBED4NO7EMEH9R.png

可以看到程序是卡死了,一直没有退出

分析

这个类非常简单,里面有一个属性,有两个方法,set、get,并且在set方法上添加了synchronized。

多线程并发的同时进行set、get操作,A线程调用set、B线程调用get能感知到flag发生变化吗?

说到这里,问题就变成了synchronized能否保证上下文可见性!!!

关键词synchronized的用法

指定加锁对象:对给定的对象进行加锁,进入同步代码前需要获得给定对象的锁。

直接作用于实例方法:相当于对当前对象的实例加锁,进入同步代码前需要获得当前对象实例的锁

直接作用于静态方法:相当于对当前类进行加锁,进入同步代码前需要获得当前类的锁。

从代码中,我们可以看到只对set方法加了同步锁,多个线程调用set方法时,由于存在锁,会一个一个的进行set,但对于get来说,并没有加锁,多个线程无需获得该实例的锁,就可以直接获取到flag的值,那么我们就需要考虑某一个线程set之后的flag对其它线程是否可见!!!

Java内存模型happens-before原则

JSR-133内存模型使用happens-before原则的概念来阐述操作之间的内存可见性。在JMM(JAVA Memory Model)中,如果一个执行的结果需要对另一个操作可见,那么这两个操作直接必须要存在happens-before关系。两个操作可以是同一个线程内的也可以是不同线程中的。

happens-before(之前发生)原则

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。

监视器锁规则:对一个监视器的解锁,happens-before于随后对这个监视器的加锁。

volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile的读。

传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

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

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

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

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

注意:两个操作之间存在happens-before关系,并不一定前一个操作必须要在后一个操作执行!!!

happens-before仅仅要求前一个操作的执行结果对后一个操作可见,且前一个操作的执行顺序排在后一个操作之前(因为java虚拟机重排不相关的指令)。

volatile

volatile可见性

前面的happens-before原则中提到了volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。因此,volatile保证了多线程下的可见性!!!

volatile禁止内存重排序

下面是JMM针对编译器制定的volatile重排序规则:

是否能重排序

第二个操作

第一个操作

普通读/写

volatile读

volatile写

普通读/写

NO

volatile读

NO

NO

NO

volatile写

NO

NO

通过上面的分析我们添加关键字volatile来试试

16RVD55TN6RF750PQD5QNCR76P.png

结论

多线程并发的同时进行set、get操作,A线程调用set方法,B线程并不一定能对这个改变可见,上面的代码中,如果get也添加synchronized也是可见的,还是happens-before的监视器规则:对一个监视器的解锁,happens-before于随后对这个监视器的加锁。只是volatile对比synchronized更轻量级,所以本例使用volatile,但是对于符合非原子操作i++这里还是不行的,还得用synchronized。

不过使用volatile也会限制一些调优

关注我获取更多咨询

qrcode_for_gh_5926b81f45c6_258.jpg

注意:本文归作者所有,未经作者允许,不得转载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值