volatile关键字

打印 上一主题 下一主题

主题 1674|帖子 1674|积分 5024

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
  1. volatile可以保证变量的可见性。
  2. 这里的变量包括类变量、实例变量,但不包括局部变量和方法参数,因为后者是线程私有的,不存在线程竞争问题
  3. java内存模型(JMM)规定,所有变量都存储在主内存中,同时每个线程还有自己的工作内存。
  4. 线程对变量的所有操作(读取、赋值等),都必须在工作内存中进行,而不能直接读写主存中的数据
  5. 不同线程也无法访问对方工作内存中的变量,线程间变量值的传递需要通过主存来完成(引自周志明《深入理解Java虚拟机》)
  6. volatile可以保证
  7. (1)任何线程更新volitale修饰的变量后都会立即同步到主存中
  8. (2)线程每次读取volitale修饰的变量,都必须到主存中取最新值
  9. 以上亮点保证了“可见性”,即任意线程对变量的修改能立即被其他线程所知
复制代码
  1. package com.concurrent;
  2. public class VolatileDemo {
  3.     // 如果不加volatile,你会发现线程2已经结束很久了,线程1还在死循环。
  4.     // 但是一旦加上volatile,线程2一执行,线程1会立刻跳出循环
  5.     // 这是因为volatile可以保证变量的可见性。
  6.     // 这里的变量包括类变量、实例变量,但不包括局部变量和方法参数,因为后者是线程私有的,不存在线程竞争问题
  7.     // java内存模型(JMM)规定,所有变量都存储在主内存中,同时每个线程还有自己的工作内存。
  8.     // 线程对变量的所有操作(读取、赋值等),都必须在工作内存中进行,而不能直接读写主存中的数据,
  9.     // 不同线程也无法访问对方工作内存中的变量,线程间变量值的传递需要通过主存来完成。
  10.     // volatile可以保证
  11.     // (1)任何线程更新volitale修饰的变量后都会立即同步到主存中
  12.     // (2)线程每次读取volitale修饰的变量,都必须到主存中取最新值
  13.     // 以上亮点保证了“可见性”,即任意线程对变量的修改能立即被其他线程所知
  14.     //
  15.     //public static Integer money = 1000;
  16.     public volatile static Integer money = 1000;
  17.     public static void main(String[] args) throws InterruptedException {
  18.         //线程1
  19.         new Thread(() -> {
  20.             while (money == 1000) {
  21.             }
  22.             System.out.println("存款已经不是" + money + "了");
  23.         }).start();
  24.         Thread.sleep(2000);
  25.         //线程2
  26.         new Thread(() -> {
  27.             money = 900;
  28.             System.out.println("存款现在是" + money);
  29.         }).start();
  30.     }
  31. }
复制代码
但是,volatile并不能保证原子性。下述代码如果能保证原子性的情况下应该返回200000,但现实执行后打印的结果远小于这个值。这是由于race++这个看似简单的操作现实上包含3个步骤:
(1)从主存获取值
(2)执行加1(iconst_1,iadd指令)
(3)写回主存
在线程执行iconst_1,i_add这些指令的时候,其他线程可能已经把race的值改变了,因此当前内存写回主存时就可能覆盖掉最新的值而把老值加1的结果写回去。(此处代码息争释均引自周志明《深入理解Java虚拟机》)
  1. package com.concurrent;
  2. public class VolatileDemo2 {
  3.     public static volatile int race = 0;
  4.     public static void increase(){
  5.         race++;
  6.     }
  7.     private static final int THREADS_COUNT = 20;
  8.     public static void main(String[] args) {
  9.         Thread[] threads = new Thread[THREADS_COUNT];
  10.         for (int i = 0; i < threads.length; i++) {
  11.             threads[i] = new Thread(()->{
  12.                 for (int j = 0; j < 10000; j++) {
  13.                     increase();
  14.                 }
  15.             });
  16.             threads[i].start();
  17.         }
  18.         while(Thread.activeCount() > 2){
  19.             Thread.yield();
  20.         }
  21.         System.out.println(race);
  22.     }
  23. }
复制代码
同时,volatile能防止指令重排。所谓指令重排,就是虚拟机在执行执行时,不考虑并发的影响,再保证结果稳定的情况下,将部分指令重新排序的征象,也就是所谓的“线程内表现为串行”(With-Thread as-if-serial semantics).
一个典型的例子是懒汉是单例的双重检测锁模式。如果不加volatile修饰,由于new对象并非原子操作,就有可能出现指令重排的征象。new对象分为三个步骤(1)分配内存空间 (2)执行构造方法,初始化对象 (3)把对象指针执行这片空间。指令重排后原本(1)(2)(3)的顺序可能酿成(1)(3)(2),这样有可能线程A执行完(1)(3),正要执行(2)的时候,线程B抢到执行权,判定lazyMan == null,为false,于是返回对象,但是此时的对象还并没有初始化,这时候去使用此对象显然会有题目。比如这里的使用就是打印,那就有可能出现先打印对象,然后再执行构造方法中的打印。但是,经过多次尝试,并没有出现1次这个题目,是概率太小还是理论有误?
  1. package com.concurrent;
  2. public class LazyMan {
  3.     private LazyMan(){
  4.         System.out.println(Thread.currentThread().getName()+"ok");
  5.     }
  6.     //private static LazyMan lazyMan = null;
  7.     private static volatile LazyMan lazyMan = null;
  8.    
  9.     //双重检测锁模式的懒汉式单例 DCL懒汉式
  10.     public static LazyMan getInstance(){
  11.         if(lazyMan == null){
  12.             synchronized (LazyMan.class){
  13.                 if(lazyMan == null){
  14.                     lazyMan = new LazyMan();
  15.                 }
  16.             }
  17.         }
  18.         return lazyMan;
  19.     }
  20.     public static void main(String[] args) {
  21.         for (int i = 0; i < 100; i++) {
  22.             new Thread(new Runnable() {
  23.                 @Override
  24.                 public void run() {
  25.                     LazyMan lazyMan = LazyMan.getInstance();
  26.                     System.out.println(lazyMan);
  27.                 }
  28.             }).start();
  29.         }
  30.     }
  31. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

拉不拉稀肚拉稀

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表