《java并发》--volatile修饰符
来源:互联网 发布:wow7.0装备数据库 编辑:程序博客网 时间:2024/05/17 22:21
volatile
参考:
Java并发:volatile内存可见性和指令重排
你真的了解volatile吗,关于volatile的那些事
java中volatile关键字的含义
- volatile
- volatile 作用
- volatile 理解
- 代码解读可见性
- 代码解读无法实现原子性
- volatile修饰避免代码重排
- 总结
volatile 作用
- 保证内存可见性
- 防止指令重排
- 不能解决原子性
volatile 理解
java中多线程共享的变量存储在主内存中,每个线程都有自己的工作内存,工作内存保存了主内存的副本,线程要操作共享变量,实际操作的是线程工作内存的副本,操作完毕后再同步写入主内存,各个线程线程只能访问自己的工作内存,不可以访问其它线程的工作内存。
java中线程工作内存跟主内存的交互
- lock:将主内存中的变量锁定,为一个线程所独占
- unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量
- read:将主内存中的变量值读到工作内存当中
- load:将read读取的值保存到工作内存中的变量副本中。
- use:将值传递给线程的代码执行引擎(多次)
- assign:将执行引擎处理返回的值重新赋值给变量副本(多次)
- store:将变量副本的值存储到主内存中。
- write:将store存储的值写入到主内存的共享变量当中。
可见性:保证线程使用共享变量时每次都去主内存获取最新的,保证了read-load的一致性
原子性:保证线程在read-load-use-assign-store-write共享变量过程中,其它线程不能对共享变量进行修改
共享变量使用volatile修饰后,保证线程每次访问共享变量都去主内存获取,保证每次获取到的是主内存中最新的值,即保证了read-load是最新的,这样就实现了可见性,但是在后续的use-assign-store-write过程中,其它线程可能会对共享变量进行操作更改,这样无法保证原子性
代码解读可见性
如果不使用volatile修饰共享变量,线程只会在第一次使用共享变量的时候去主内存加载建立副本,这样子线程永远不会停止
使用volatile修饰修饰共享变量,在while循环的判断running值的时候,每次都去主内存获取最新的值,当主线程将running设置为false的时候,停止子线程,在while循环中使用了count变量,如果只将count用volatile修饰,也能停止子线程,由此可见,线程去主内存读取共享变量的时候,会把所有用到的共享变量都在工作内存建立副本
public class Task implements Runnable{ //将count用volatile修饰,保证每次去主存读取count值, //读取的同时会将running也从主存读取,不管running是否用volatile修饰 private volatile int count = 0; private boolean running = true; @Override public void run() { while(running){ // count++; } System.out.println("子线程"+Thread.currentThread().getName()+"停止"); } public static void main(String[] args) throws InterruptedException { Task task = new Task(); //启动子线程 new Thread(task).start(); Thread.currentThread().sleep(3000); task.setRunning(false); System.out.println("主线程停止"); } public void setRunning(boolean running) { this.running = running; } public int getCount() { return count; }}
代码解读无法实现原子性
下面这段程序执行完毕后无法保证count的数量最终为1000,这是因为volatile只能保证使用count的时候去主内存读取到最新的值,但是在对count进行+1操作的时候,其它线程可能会对count进行修改+1然后写会主内存,造成最后的结果不是1000,如果要保证1000,还是要对整个read到write回主内存保证一致性,这就需要使用synchronized或者lock去实现了。
public class Counter { //使用volatile修饰共享变量 public volatile static int count = 0; public static void inc() { // 这里延迟1毫秒,使得结果明显 try { Thread.sleep(1); } catch (InterruptedException e) { } //无法保证是1000 count++; } public static void main(String[] args) { // 同时启动1000个线程,去进行i++计算 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); } }).start(); } // 无法保证count值为1000 System.out.println("运行结果:Counter.count=" + Counter.count); }}
volatile修饰避免代码重排
指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。编译器、处理器也遵循这样一个目标。注意是单线程。多线程的情况下指令重排序就会给程序员带来问题。
内存屏障:在使用volatile修饰的变量前后插入一个内存栅栏,告诉JVM该条指令不能跟前后语句进行重排。
指令重排在多线程操作的时候,如果变量没有使用volatile修饰,可能会出现问题
//线程1初始化UserUser user;user = new User();//线程2读取userif(user!=null){ user.getName();}
具体来看User user = new User的语义:
1:分配对象的内存空间
2:初始化对线
3:设置user指向刚分配的内存地址操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM是可以针对它们进行指令的优化重排序的,优化后变为 1->3->2
这些线程1在执行完第3步而还没来得及执行完第2步的时候,如果内存刷新到了主存,那么线程2将得到一个未初始化完成的对象。
//在线程A中:context = loadContext();inited = true;//在线程B中:while(!inited ){ //根据线程A中对inited变量的修改决定是否使用context变量 sleep(100);}doSomethingwithconfig(context);//假设线程A中发生了指令重排序:inited = true;context = loadContext();//那么B中很可能就会拿到一个尚未初始化或尚未初始化完成的context,从而引发程序错误。
volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。懒汉式单例模式就是使用volatile防止创建多个实例对象
总结
- volatile无法实现原子性,只能实现可见性
- 当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。
- 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
- 在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的。尤其在、jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升。
- 当且仅当满足以下所有条件时,才应该使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
- 该变量没有包含在具有其他变量的不变式中,防止影响其他变量??
- 防止代码重排
- 《java并发》--volatile修饰符
- java修饰符:volatile关键字
- Java中Volatile 修饰符
- Java的volatile修饰符
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- java并发编程(五)--volatile变量修饰符—意料之外的问题
- Java并发编程(5):volatile变量修饰符—意料之外的问题(含代码)
- java挑战高并发(5):volatile变量修饰符的使用与问题
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- Javaweb环境搭建与项目部署
- Java中获取用户输入值的四种方法
- 怎么有效杀蚊子,长效杀蚊,灭绝
- NSURLSession详解
- TVS管原理、特性和选型及失效简析
- 《java并发》--volatile修饰符
- MATLAB报错:未定义函数或变量
- jquery easyui中datagrid数据传递和返回的格式
- nginx+php+mysql 搭建完整web服务器
- ajax学习笔记
- POJ3614_Sunscreen_贪心 && stl的优先级队列
- 1067
- Devexpress MVVM OpenFileDialogService&&SaveFileDialogService
- 进程同步与互斥笔记