volatile关键字的2个作用

1.线程的可见性

2.防止指令重排

什么是线程的可见性?

线程的可见性 就是一个线程对一个变量进行更改操作 其他线程获取会获得最新的值。

线程在执行的行 操作主线程的变量。会将变量的副本拷贝一份到线程的工作区域(避免每次到主线程读取 提高效率),在更改后的一段时间内写入主内存

如下示例代码:

public class Accounting implements Runnable {
boolean quit=false;
int i=0;
@Override
public void run() {
while (!quit){
i++;
}
System.out.println("线程退出");
}
public static void main(String[] args) throws InterruptedException {
Accounting accounting = new Accounting();
Thread a1 = new Thread(accounting, "a1");
Thread a2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("开始通知线程结束");
accounting.setQuit(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
a2.start();
a1.start();
Thread.sleep(1000);
} public boolean isQuit() {
return quit;
} public void setQuit(boolean quit) {
this.quit = quit;
}
}

这段代码的逻辑就是线程a1 执行循环操作  a2 2秒后设置quit为true任务结束 打印 "线程退出";

那么真的能够成功退出吗?我们看看 线程执行在内存中的操作图

打印:

开始通知线程结束

a2 线程首先将自己工作线程的quit改为ture ,然后一定时间之后去将主内存的quit改为true  ,但是a1线程始终是操作的是自己的工作内存的副本 所以死循环

这个时候在quit加上volatile关键字

  volatile boolean quit=false;

打印

开始通知线程结束
线程退出

加上volatile关键字后。当一个线程对变量进行修改会更新自己的工作内存里面的值,然后立即将改动的值刷新到主内存,同时线程2的工作内存的quit副本缓存失效  下次直接到主内存读取  所以能够正常执行

记录一个小插曲

System.out.println,sychronized,Thread.sleep Thread.sleep 影响可见性?

System.out.println

public class Accounting implements Runnable {
boolean quit=false;
int i=0;
@Override
public void run() {

while (!quit){
i++;
System.out.println(i);
}
System.out.println("线程退出");
}
public static void main(String[] args) throws InterruptedException {
Accounting accounting = new Accounting();
Thread a1 = new Thread(accounting, "a1");
Thread a2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("开始通知线程结束");
accounting.setQuit(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
a2.start();
a1.start();
Thread.sleep(1000); } public boolean isQuit() {
return quit;
} public void setQuit(boolean quit) {
this.quit = quit;
}

会发现没有加上volatile一样可以成功退出 。那我们上面说的 线程的内存处理 不成立了吗?

查资料说 是因为jvm对锁的优化。因为如果我们在循环里面加上sychronize同步锁 会产生大量的锁竞争 所以jvm优化过后

   synchronized (this){
while (!quit){
//.....
}
}

但是我们并没有在while里面加锁啊。我们看看打印的方法源码

    public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}

sleep方法并没有加锁,为什么能够保证可见性

sleep是阻塞线程并不释放锁,让出cpu调度。 让出cpu调度后下次执行会刷新工作内存

指令重排

指令重排指在编译的时候,在不单线程运行不影响结果的情况下进行指令优化

如:

public class Context {
boolean isLoad=false;
Object configuration=null;
public void loadConfiguration(){
System.out.println("正在加载配置文件");
configuration= new Object();
isLoad=true;
} public void initContext(){
System.out.println("正在进行初始化");
} public static void main(String[] args) {
Context context=new Context();
context.loadConfiguration();
if(context.isLoad){
context.initContext();
}
}
}

这段代码就是先加载配置文件信息  然后初始化上下文

我们在单线程下 把他们的顺序调换模拟指令重排 会对结果没有影响

  public void   loadConfiguration(){
isLoad=true;
System.out.println("正在加载配置文件");
configuration= new Object(); }

但是在多线程下面

public class Context {
boolean isLoad=false;
Object configuration=null;
public void loadConfiguration(){
//模拟jvm指令重排 将isLoad命令排在第一位
isLoad=true;
/***
* 模拟并发情况下指令重排。导致的isload=true排到前面。
* 这个时候配置文件没初始化。initContext监听到lsLoad等于true根据配置文件进行初始化
*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
configuration= new Object();
//isLoad=true;指令重排前
} public void initContext(){
configuration.toString();
System.out.println("正在进行初始化");
} public static void main(String[] args) {
Context context=new Context();
//负责监听 如果加载完毕 则进行上下午初始化
Thread t2=new Thread(new Runnable() {
@Override
public void run() { while (true){
if(context.isLoad){
context.initContext();
break;
}
} }
},"t2");
//负责加载配置文件
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
context.loadConfiguration();
}
},"t1");
t1.start();
t2.start();
}
}

只是模拟指令重排 先不考虑可见性  这种情况会初始化context 没有configuration 报错  使用volatile关键字修饰可以避免

值得注意的一点

volatile虽然能够保证线程的可见性 但是并不能保证原子性  比如i++操作 都是读出i的值 进行运算再写入。如果在读出的时候别的线程改变了 就会不一致

哪种场景适合用volatile 对一个变量的值进行修改 不依赖其他值。 比如 index=true   而不是i=i+j;或则index=j>a   或 a=j (会从内存中读出j的值 然后赋值到a);

java提供atomic cas能够性能比锁高能够保证原子性 如:atomicInt atomictDouble

最新文章

  1. ASP.NET 5运行时升级到Beta5
  2. mysql二级索引
  3. 关于ellipsis多行换行的方案
  4. 日志分析 第七章 安装grafana
  5. js 的闭包
  6. node
  7. DES算法详解
  8. mongoose学习笔记2--增删改查1
  9. SQL 面试题(一)
  10. 第一个 MIC shared_memory 程序
  11. 【BZOJ】1012: [JSOI2008]最大数maxnumber 树状数组求区间最值
  12. 12个强大的Web服务测试工具
  13. 关于jsp web项目中的javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/el/ELResolver错误
  14. robot framework环境搭建和简单示例
  15. 在centos中配置maven
  16. 使用anyremote进行远程鼠标控制
  17. Jenkins : 邮件通知
  18. 第5章 Linux上管理文件系统
  19. 真的分治fft
  20. Assembly.LoadFrom加载程序集类型转换失败解决方法

热门文章

  1. 当使用servlet输出json时,浏览器端jquery的ajax遇到parse error的问题
  2. gephi——怎样上传节点表格而且为节点设定颜色类型
  3. 再探Linux动态链接 -- 关于动态库的基础知识(Dynamic Linking on Linux Revisited)
  4. typeof、instanceof、hasOwnProperty()、isPrototypeOf()
  5. SQL Server的自动备份设置及排错记事
  6. Java中利用随机数的猜拳游戏
  7. Mysql数据库概述
  8. 通过阅读《React 进阶之路》之学习笔记
  9. HTTP获取信息的四种方式
  10. hdu3861 The King’s Problem 强连通缩点+DAG最小路径覆盖