Volatile关键字详解

  • 简介

  在java中,每一个线程有一块工做内存区,其中存放这被全部线程共享的主内存中变量值的拷贝。当线程执行时,它在本身的工做内存中操做这些变量。为了获取一个共享变量,一个线程先获取锁定并清除它的工做内存区,这就保证了该共享变量从全部的线程的共享主内存区正确的装入到线程的工做内存区,当线程解锁时保证该工做内存区的变量的值写回到共享主内存区。java

  线程工做内存和主内存的交互图以下:缓存

  从上图中能够看出,主内存和线程工做内存间的数据传输与线程工做内存和线程执行有必定的时间间隔,并且每次所消耗的时间可能还不相同,这样就存在线程操做的数据的不一致性。因为每一个线程都有本身的线程工做内存,所以当一个线程改变本身工做内存中的数据的时候,对于其余系统来讲多是不可见的。所以,使用volatile关键字迫使全部的线程均读写主内存中对应的变量,从而使得volatile关键字修饰的变量在多线程间可见。多线程

  volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工做内存的值是最新的。app

  声明为volatile的变量具备以下特性:jvm

  一、其余线程对变量的修改能够即时反映在当前线程中。ide

  二、确保当前线程对volatile变量的修改,能即时的写回到共享主内存中,并被其余线程所见。性能

  三、使用volatile修饰的变量,编译器会保证其有序性。测试

  • volatile分析

  用在多线程,同步变量。 线程为了提升效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动做时才进行A和B的同步。所以存在A和B不一致的状况。volatile就是用来避免这种状况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A) atom

  下面一个测试例子:线程

public class MyThread extends Thread{
     private volatile  boolean stop = false;//确保stop在多线程中可见
     
     public void stopMe(){
         stop = true;
         System.out.println("stopMe"+System.currentTimeMillis());
     }
 
     @Override
     public void run() {
         int i = 0;
         while(!stop){
             i++;
         }
         System.out.println("run"+System.currentTimeMillis());
         System.out.println("stop thread");
     }
     
 }

  若是stop没有被声明为volatile类型,那么线程在执行run的时候是检查本身的工做内存的副本,不能得知其余线程对stop的修改,所以线程没法结束。可是将stop被声明为volatile类型,那么在其余线程修改stop后,线程会马上知道,则会跳出循环,正常结束。

  volitile与synchronized的区别

  Volatile通常状况下不能代替sychronized,由于volatile不能保证操做的原子性,即便只是i++,实际上也是由多个原子操做组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操做的i是同一块内存,但依然可能出现写入脏数据的状况。若是配合Java 5增长的atomic wrapper classes,对它们的increase之类的操做就不须要sychronized。

  synchronized得到并释放监视器——若是两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行。volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized经过锁定和解锁某个监视器同步全部变量的值。显然synchronized要比volatile消耗更多资源。

  在使用volatile关键字时要慎重,并非只要简单类型变量使用volatile修饰,对这个变量的全部操做都是原来操做,当变量的值由自身的上一个决定时,如n=n+一、n++ 等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操做才是原子级别的,如n = m + 1,这个就是原级别的。因此在使用volatile关键时必定要谨慎,若是本身没有把握,可使用synchronized来代替volatile。 volatile 变量不会像锁那样形成线程阻塞,在某些状况下,若是读操做远远大于写操做,volatile 变量还能够提供优于锁的性能优点。

  • 原理

  若是对声明了Volatile变量进行写操做,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。可是就算写回到内存,若是其余处理器缓存的值仍是旧的,再执行计算操做就会有问题,因此在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每一个处理器经过嗅探在总线上传播的数据来检查本身缓存的值是否是过时了,当处理器发现本身缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操做的时候,会强制从新从系统内存里把数据读处处理器缓存里。

  lock前缀指令实际上至关于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

 

  1)它确保指令重排序时不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操做已经所有完成;

 

  2)它会强制将对缓存的修改操做当即写入主存;

 

  3)若是是写操做,它会致使其余CPU中对应的缓存行无效。

 

    把对volatile变量的单个读/写,当作是使用同一个监视器锁对这些单个读/写操做作了同步。下面咱们经过具体的示例来讲明,请看下面的示例代码:

class VolatileFeaturesExample {
     volatile long vl = 0L;  //使用volatile声明64位的long型变量
     public void set(long l) {
         vl = l;   //单个volatile变量的写
     }
     public void getAndIncrement () {
         vl++;    //复合(多个)volatile变量的读/写
     }
     public long get() {
         return vl;   //单个volatile变量的读
     }
 }

  假设有多个线程分别调用上面程序的三个方法,这个程序在语意上和下面程序等价:

class VolatileFeaturesExample {
     long vl = 0L;               // 64位的long型普通变量
     public synchronized void set(long l) {     //对单个的普通 变量的写用同一个监视器同步
         vl = l;
     }
     public void getAndIncrement () { //普通方法调用
         long temp = get();           //调用已同步的读方法
         temp += 1L;                  //普通写操做
         set(temp);                   //调用已同步的写方法
     }
     public synchronized long get() { 
     //对单个的普通变量的读用同一个监视器同步
         return vl;
     }
 }
  • Volatile与synchronized的区别:

  synchronized得到并释放监视器——若是两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。可是,synchronized也同步内存:事实上,synchronized在“ 主”内存区域同步整个线程的内存。

  volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized经过锁定和解锁某个监视器同步全部变量的值。显然synchronized要比volatile消耗更多资源。

  Volatile只能保证可见性和有序性,对任意单个volatile变量的读/写具备原子性,全部在对volatile变量进行操做的时候要保证其操做是原子性,不然就要加锁来保证原子性。

  • Volatile的使用必须知足的条件

  一、对变量的写操做不依赖于当前值。

  二、该变量没有包含在具备其余变量的不变式中。

相关文章
相关标签/搜索