文档章节

Volatile关键字详解

wangtx
 wangtx
发布于 2016/05/11 22:48
字数 2070
阅读 145
收藏 3
  • 简介

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

  线程工作内存和主内存的交互图如下:

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

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

  声明为volatile的变量具有如下特性:

  1、其他线程对变量的修改可以即时反映在当前线程中。

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

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

  • volatile分析

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

  下面一个测试例子:

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+1、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的使用必须满足的条件

  1、对变量的写操作不依赖于当前值。

  2、该变量没有包含在具有其他变量的不变式中。

本文转载自:http://hovertree.com/h/bjaf/tnnncsni.htm

共有 人打赏支持
wangtx
粉丝 7
博文 29
码字总数 21340
作品 0
济南
程序员
私信 提问
C/C++中volatile关键字详解

1. 为什么用volatile?   C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 “The C++ Programming Language” 对 volatile 修...

fx677588
2017/08/13
0
0
volatile关键字详解

一、Java中的volatile 在Java程序中,如果一个变量被volatile关键字修饰,那么这个变量就具有了有序性和可见性。 有序性:java语言中提供了synchronized和volatile两个关键字保证线程之间操作...

J星星点灯
2017/09/11
0
0
详解C中volatile关键字

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取...

jackxu2015
2015/08/14
0
0
详解C中volatile关键字

在PHP官网上看到一个浮点数BUG,测试代码 。在SVN里看了一下修复方法:在变量的声明前加了个volatile关键字。不知道这个是什么意思,特意去网上查了一下,找到了这篇文章,写得不错,转载一下...

苗雨顺
2011/02/09
0
0
java中volatile关键字的详解

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。 Java语言是支持多线程的,为了解决线程并发的问题...

子群
2016/12/08
4
0

没有更多内容

加载失败,请刷新页面

加载更多

java框架学习日志-7(静态代理和JDK代理)

静态代理 我们平时去餐厅吃饭,不是直接告诉厨师做什么菜的,而是先告诉服务员点什么菜,然后由服务员传到给厨师,相当于服务员是厨师的代理,我们通过代理让厨师炒菜,这就是代理模式。代理...

白话
39分钟前
6
0
Flink Window

1.Flink窗口 Window Assigner分配器。 窗口可以是时间驱动的(Time Window,例如:每30秒钟),也可以是数据驱动的(Count Window,例如:每一百个元素)。 一种经典的窗口分类可以分成: 翻...

满小茂
40分钟前
3
0
my.ini

1

architect刘源源
56分钟前
6
0
docker dns

There is a opensource application that solves this issue, it's called DNS Proxy Server It's a DNS server that solves containers hostnames, if could not found a hostname that mat......

kut
今天
5
0
寻找数学的广度——《这才是数学》读书笔记2700字

寻找数学的广度——《这才是数学》读书笔记2700字: 文|程哲。数学学习方式之广:国内外数学教育方面的专家,进行了很多种不同的数学学习方式尝试,如数学绘本、数学游戏、数学实验、数学步道...

原创小博客
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部