关键字volatile的含义及使用,volatile关键字在什么阶段起作用
如何解决写爬虫IP受阻的问题?立即使用。
深入理解volatile关键字
1.volatile与可见性
大家都知道volatile可以保证可见性,那么是怎么保证的呢?
这就方便了“发生之前”的原则。这个原理的第三条规则规定:对于volatile修改的变量,写操作早于这个变量的读操作。具体步骤如下:
线程A将共享变量读入工作内存,线程B也将共享变量读入工作内存。
线程A修改共享变量后,会立即刷新到主存。此时,线程B的工作内存中的共享变量将被设置为无效,需要从主存中重新读取新值。反映硬件上CPU的缓存线被设置为无效状态。
这确保了可视性。简单来说,一个线程修改volatile修改的变量并刷新到主存后,会使其他线程工作内存中的共享变量失效,需要重新从主存中读取。
2.volatile与有序性
大家都知道volatile可以保证秩序,那怎么保证呢?
Volatile保证有序,相对直接。禁止JVM和处理器对volatile关键字修改的变量的指令进行重新排序,但变量之前或之后的变量可以任意排序,只要最终结果与未修改的结果一致即可。
底层原理
volatile修改的变量会在底部加上“lock:”前缀,带‘lock’前缀的指令相当于一个内存屏障,是保证可见性和顺序的关键。该屏障的功能如下:
指令重排时,不能将屏障前的代码重排到屏障后,也不能将屏障后的代码重排到屏障前。
在执行内存屏障时,要确保前面的代码都已经执行了,并且屏障后面的代码可以看到执行结果。
强制将工作内存中的变量刷新到主内存中。
其他线程的工作内存中的变量将被设置为无效,需要从主内存中重新读取。
3.volatile与原子性
大家都知道volatile不能保证原子性,为什么不能呢?
代码演示:
包com . github . excellent 01;
导入Java . util . concurrent . countdownlatch;
/**
* @作者plg
* @日期2019/5/19 9:37
*/
公共类TestVolatile实现Runnable {
私有可变整数num=0;
私有静态CountDownLatch latch=new CountDownLatch(10);
@覆盖
公共无效运行(){
for(int I=0;i 1000i ){
num
}
latch . count down();
}
公共整数getNum() {
退货数量;
}
公共静态void main(String[] args)引发InterruptedException {
test volatile test=new test volatile();
for(int I=0;i 10i ){
新线程(测试)。start();
}
latch . await();
system . out . println(test . getnum());
}
}启动10个线程,每个线程给共享变量num加1000次。当所有线程完成后,打印出num的最终结果。
很少有一万个,这是因为volatile不能保证原子性。
原因分析:
num++的操作由三步组成:
将num从主存储器读入工作存储器
在工作记忆中增加一个。
一旦加法完成,就把它写回主存。
这三个步骤虽然都是原子操作,但放在一起不是原子操作,每个步骤在执行的过程中都有可能被打断。
此时假设num的值为10,线程A将变量读入自己的工作内存。此时发生CPU切换,B也将num读入自己的工作内存。此时线程B修改了自己工作内存中num的值,变成了11,但是还没有刷新到主存,所以线程A不知道num的值已经改变了。如前所述,修改volatile变量后,其他线程会立即知道,前提是先刷新到主存中,然后其他线程会将自己工作中共享变量的值设置为无效。因为没有刷新到主存,A傻乎乎的不知道给10加了一,所以最后虽然两个线程都加了一个,但是最后结果只加了一次。
这就是为什么volatile不能保证原子性。
volatile的使用场景
根据volatile的特性,可以保证有序性和可见性,但不能保证原子性。因此,volatile可以用于不需要原子性,或者原子性已经得到保证的情况:
代码演示
可变布尔关闭请求
公共void shutdown() {
shutdownRequested=true
}
公共无效工作(){
while(关闭请求){
//做事情
}
}只要线程修改shutdownRequested,执行工作的线程就会立刻看到,所以会立刻停止。如果没有添加volatile,那么每次它去工作内存读取数据的时候总是真的。它一直执行,不知道别人已经停止了。
代码演示:
包com . github . excellent;
导入Java . util . concurrent . threadpoolexecutor;
/**
*启动线程会被阻塞,从内存中读取标志并存入寄存器,下次直接从寄存器中取值。
*所以值总是假的。
*即使另一个线程更改了值,它也不知道。
*添加挥发物。也可以锁定,只要保证内存可见性。
* @作者plg
* @日期2019/5/2 22:40
*/
公共类Testvolatile {
公共静态布尔标志=false
公共静态void main(String[] args)引发InterruptedException {
Thread thread1=新线程(()-{
for(;) {
system . out . println(flag);
}
});
Thread thread2=新线程(()-{
for(;){
flag=true
}
});
thread 1 . start();
thread . sleep(1000);
thread 2 . start();
}
}执行结果:
太蠢了,别人修改了,自己不知道,还输出false。加个挥发物就行了。以上是深入了解volatile关键词的细节。更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。