多线程(五):Volatile 关键字

来源:互联网 发布:三星g925a支持的网络 编辑:程序博客网 时间:2024/05/20 07:31
包括:
一. volatile概念
二.使用volatile需要注意的地方

一. volatile 关键字

       volatile是一个比较轻量级的同步,用于修饰变量,不能用于修饰方法。当一个变量被声明成 volatile,任何对该变量的读写操作都会绕过高速缓存,直接读写主内存,这表示所有线程在任何时候看到的 volatile 变量值都相同。
优缺点:
  • 优点:更少的代码量来实现多线程,而且比较好理解,无须太多的学习成本。
  • 缺点:volatile只能保证变量的可见性,无法保证原子性。除此之外,由于volatile屏蔽掉了VM中必要的代码优化,所以在效率上会稍微低点。这是两个缺点。
二. 使用volatile 需要注意的地方
       volatile也有使用前提,并不是说使用了volatile就一定线程安全。在《深入理解Java虚拟机》一书中,明确写道:由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁来保证原子性:
1. 运算结果并不依赖于当前值,或者能够确保只有单一的线程能够修改变量的值。
2. 变量不需要与其他的状态变量共同参与不变约束。

怎么理解,最好的贴代码:
程序1:
public class main {public static int i = 0;public static void main(String args[]){new Thread(new Runnable(){public void run(){for(int j = 0; j < 10000; j++)i++;System.out.println("Thread1 end...");}}).start();;new Thread(new Runnable(){public void run(){for(int j = 0; j < 10000; j++)i++;System.out.println("Thread2 end...");}}).start();i++;try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("i = " + i);}}

      这个程序很好理解,创建一个volatile的变量,主线程中创建2个线程修改volatile变量的。这两个线程最后分别有一句System.out.println("Thread2 end..."); 也就是说,这两句都打印出来起码 i 的值也有20000了。那么实际结果中大多数会能正确。但是,少数会出现下面这些结果(试了我好多次,手都累了):
Thread1 end...Thread2 end...i = 18362

       分析一下为什么会这样,说好的volatile保证线程安全呢?
       比如说,线程A先从主内存中取得 i 的值等于0,接着线程 A对i进行 +1 的修改,接着 新的 i 值不会经过本地内存,而是直接写到主内存中,这个时候主内存中的 i 值变为 1 。此时线程B 做和线程A同样的操作,从主内存中取值,然后相加,再直接更改到主内存。这种情况下,是正常的。i 的值最后会正常。
       但是下面这种情况就不正常了:线程A先从主内存中取得 i 的值等于0,此时,还没有等到 线程 A 修改值,线程B 就已经从主内存中拿 i的值了,此时线程B拿到的值也是0,这样的话,就会出问题。因为理应线程B要拿线程A修改后的值。这个也说明了,volatile不保证原子性,只保证可见性
        归根结底,这个程序就是违反了第一个原则:只能有单一线程修改变量的值。

程序2:
private Date start;  private Date end;    public void setInterval(Date newStart, Date newEnd) {      // 检查start<end是否成立, 在给start赋值之前不变式是有效的      start = newStart;        // 但是如果另外的线程在给start赋值之后给end赋值之前时检查start<end, 该不变式是无效的        end = newEnd;      // 给end赋值之后start<end不变式重新变为有效  }


归根结底,这个程序就是违反了第二个原则:变量不需要与其他的状态变量共同参与不变约束


        那么什么时候最适合使用volatile。那就是当做标记来进行使用,比如说,当shutdown()方法被调用的时候,所有的doWork()方法都停下来。


Ps:
可见性,原子性:
       可见性是指线程之间的可见性,一个线程修改的状态对于另外一个线程来说是可见的。也就是一个线程修改的结果对于另外一个线程来说是马上可见的。
       原子是世界上最小的单位,具有不可分割性。比如说a=0,这个操作就是不可分割的,那么就具有原子性。比如说i++,可以拆分为i = i + 1,是可分割的,所以它就不具有原子性。非原子性操作都会存在线程安全的问题


参考:
1.java并发之原子性与可见性(一):http://www.360doc.com/content/14/0521/11/7385274_379575711.shtml
2.变量的可变性和volatile:http://coolxing.iteye.com/blog/1464501





0 0
原创粉丝点击