volatile关键字

来源:互联网 发布:mac版office彻底删除 编辑:程序博客网 时间:2024/04/27 17:07

看网上各大公司的面经时,volatile关键字经常出现在里面,所以我研究了一下这个变量

熟悉一下并发事件中的两个概念:
1. 可见性:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。
2.原子性:原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

再强调一遍:这个操作同样存在线程安全问题!!!

  • 解释:
    Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
      在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

那么我们会有疑问,根据它的作用,volatile怎么使用呢?

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
用volatile和不用volatile的区别,运行一下,就知道了。
不用volatile:

package com.keyword;  public class TestWithoutVolatile {      private static boolean bChanged;      public static void main(String[] args) throws InterruptedException {          new Thread() {              @Override              public void run() {                  for (;;) {                      if (bChanged == !bChanged) {                          System.out.println("!=");                          System.exit(0);                      }                  }              }          }.start();          Thread.sleep(1);          new Thread() {              @Override              public void run() {                  for (;;) {                      bChanged = !bChanged;                  }              }          }.start();      }  }  

运行后,程序进入死循环了,一直在运行。
使用volatile:

package com.keyword;  public class TestWithVolatile {      private static volatile boolean bChanged;      public static void main(String[] args) throws InterruptedException {          new Thread() {              @Override              public void run() {                  for (;;) {                      if (bChanged == !bChanged) {                          System.out.println("!=");                          System.exit(0);                      }                  }              }          }.start();          Thread.sleep(1);          new Thread() {              @Override              public void run() {                  for (;;) {                      bChanged = !bChanged;                  }              }          }.start();      }  }  

程序输出!=,然后马上退出

原因:Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。所以第一个程序获取变量的值的时间很短,bChanged == !bChanged条件始终不成立,进入了死循环。
而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。所以第二个程序在比较bChanged == !bChanged时,由于获取共享成员变量的时间较长,在这个时间bChanged的值被另一个线程改变,bChanged的值发生了变化导致了条件的成立。

看看JDK里使用volatile的类。

  • 比如java.util.regex.Pattern里的变量:
    [java] view plain copy 在CODE上查看代码片派生到我的代码片
    private transient volatile boolean compiled = false;

  • 还有,java.lang.System的变量:
    [java] view plain copy 在CODE上查看代码片派生到我的代码片
    private static volatile Console cons = null;

一般就是初始化的时候,需要用到volatile。

  • java.util.Scanner里的变量如:
    [java] view plain copy 在CODE上查看代码片派生到我的代码片
    private static volatile Pattern boolPattern;
    private static volatile Pattern separatorPattern;
    private static volatile Pattern linePattern;
  • 初始化boolPattern的代码
    [java] view plain copy 在CODE上查看代码片派生到我的代码片
    private static Pattern boolPattern() {
    Pattern bp = boolPattern;
    if (bp == null)
    boolPattern = bp = Pattern.compile(BOOLEAN_PATTERN,
    Pattern.CASE_INSENSITIVE);
    return bp;
    }

上面的情况,可以使用synchronized来对boolPattern加锁,但是synchronized开销比volatile大,volatile能够胜任上面的工作。


参考自:
http://blog.csdn.net/feier7501/article/details/20001083

0 0
原创粉丝点击