volatile修饰符使用场景
在多线程并发编程中,经常使用的是线程锁,例如 synchronized
,其实在某些特定场景下,可以使用volatile
这个轻量级的 “synchronized” 来实现。volatile
它在多线程编程中能保共享变量的可见性。可见性的意思按照并发编程的艺术这边书上描述的就是:“当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值”。
书面说法的volatile使用场景:
- 运算结果并不依赖变量的当前值(即结果对产生中间结果不依赖),或者能够确保只有单一的线程修改变量的值
- 变量不需要与其它的状态变量共同参与不变约束
这里就不画什么内存图,做什么底层原理描述了,书上的描述绝对比我自己的口水话清楚,这里算是看并发编程的艺术这本书的时候,因为volatile这个修饰符用的场景比较少,做个记录,下面用代码跑一下。
使用场景
和synchronized
不通,在多线程的操作下,不保证变量的原子性,也就是说在多线程下只保证能取出当前最新值,而不保证在多线程同时操作变量的那一瞬间是唯一的,原子性的操作。
- volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
package com.wzh.demo.concurrentProgrammingArt.lock;
/**
* @desc: volatile修饰符测试
* @author: wzh
* @date: 2019年4月14日
*/
public class VolatileTest implements Runnable{
private boolean running = true;
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void run() {
while(running){
}
System.out.println("子线程"+Thread.currentThread().getName()+"停止");
}
@SuppressWarnings("static-access")
public static void main(String[] args) throws InterruptedException {
VolatileTest task = new VolatileTest();
//启动子线程
new Thread(task).start();
Thread.currentThread().sleep(1000);
task.setRunning(false);
System.out.println("主线程停止");
}
}
这段代码运行会发现子线程不会停止,因为没有错操作去内存中都数据,子线程中的running一直是true
代码修改一下,running 加volatile修饰符
package com.wzh.demo.concurrentProgrammingArt.lock;
/**
* @desc: volatile修饰符测试
* @author: wzh
* @date: 2019年4月14日
*/
public class VolatileTest implements Runnable{
private volatile boolean running = true;
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void run() {
while(running){
}
System.out.println("子线程"+Thread.currentThread().getName()+"停止");
}
@SuppressWarnings("static-access")
public static void main(String[] args) throws InterruptedException {
VolatileTest task = new VolatileTest();
//启动子线程
new Thread(task).start();
Thread.currentThread().sleep(1000);
task.setRunning(false);
System.out.println("主线程停止");
}
}
运行结果
主线程停止
子线程Thread-0停止
总结
- volatile无法实现原子性,只能实现可见性
- 当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。
- 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字
- 在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的。尤其在、jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升。
- 当且仅当满足以下所有条件时,才应该使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
- 该变量没有包含在具有其他变量的不变式中,防止影响其他变量
- 防止代码重排
网友评论