Java中关键字volatile的作用

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

  在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。

  一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。以下例子展现了volatile的作用:


public class StoppableTask extends Thread {

private volatile boolean pleaseStop;

public void run() {

while (!pleaseStop) {

// do some stuff...

}

}

public void tellMeToStop() {

pleaseStop = true;

}

}

public class StoppableTask extends Thread {

private volatile boolean pleaseStop;

public void run() {

while (!pleaseStop) {

// do some stuff...

}

}

public void tellMeToStop() {

pleaseStop = true;

}

}

  假如pleaseStop没有被声明为volatile,线程执行run的时候检查的是自己的副本,就不能及时得知其他线程已经调用tellMeToStop()修改了pleaseStop的值。

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

 恐怕比较一下volatile和synchronized的不同是最容易解释清楚的。volatile是变量修饰符,而synchronized则作用于一段代码或方法;看如下三句get代码:


int i1;

int geti1() {return i1;}

volatile int i2;

int geti2()

{return i2;}

int i3;

synchronized int geti3() {return i3;}

geti1()

int i1;

int geti1() {return i1;}

volatile int i2;

int geti2()

{return i2;}

int i3;

synchronized int geti3() {return i3;}

geti1()

  得到存储在当前线程中i1的数值。多个线程有多个i1变量拷贝,而且这些i1之间可以互不相同。换句话说,另一个线程可能已经改变了它线程内的i1值,而这个值可以和当前线程中的i1值不相同。事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2,线程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,而且这个改变还没来得及传递给“主”内存区域或其他线程时就会发生。

  而 geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝。换句话说,一个变量经 volatile修饰后在所有线程中必须是同步的;任何线程中改变了它的值,所有其他线程立即获取到了相同的值。理所当然的,volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。

  既然volatile关键字已经实现了线程间数据同步,又要 synchronized干什么呢?呵呵,它们之间有两点不同。首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。但是,synchronized也同步内存:事实上,synchronized在“ 主”内存区域同步整个线程的内存。因此,执行geti3()方法做了如下几步:

  1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)

  2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗])

  3. 代码块被执行

  4. 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)

  5. 线程释放监视this对象的对象锁

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

  =========================分割线3=================================

  volatile关键字相信了解Java多线程的读者都很清楚它的作用。volatile关键字用于声明简单类型变量,如int、float、 boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。例如,下面的例子中的n就不是原子级别的:


package  mythread;

public   class  JoinThread  extends  Thread

{

public   static volatile int  n  =   0 ;

public   void  run()

{

for  ( int  i  =   0 ; i  <   10 ; i ++ )

try

{

n  =  n  +   1 ;

sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒

}

catch  (Exception e)

{

}

}

public   static   void  main(String[] args)  throws  Exception

{

Thread threads[]  =   new  Thread[ 100 ];

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  建立100个线程

threads[i]  =   new  JoinThread();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  运行刚才建立的100个线程

threads[i].start();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  100个线程都执行完后继续

threads[i].join();

System.out.println( " n= "   +  JoinThread.n);

}

}

package  mythread;

public   class  JoinThread  extends  Thread

{

public   static volatile int  n  =   0 ;

public   void  run()

{

for  ( int  i  =   0 ; i  <   10 ; i ++ )

try

{

n  =  n  +   1 ;

sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒

}

catch  (Exception e)

{

}

}

public   static   void  main(String[] args)  throws  Exception

{

Thread threads[]  =   new  Thread[ 100 ];

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  建立100个线程

threads[i]  =   new  JoinThread();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  运行刚才建立的100个线程

threads[i].start();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  100个线程都执行完后继续

threads[i].join();

System.out.println( " n= "   +  JoinThread.n);

}

}

如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n=n+1不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:

  n  =  n  +   1 ;

  n ++ ;

  如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:


package  mythread;

public   class  JoinThread  extends  Thread

{

public   static int  n  =   0 ;

public static   synchronized   void  inc()

{

n ++ ;

}

public   void  run()

{

for  ( int  i  =   0 ; i  <   10 ; i ++ )

try

{

inc();  //  n = n + 1 改成了 inc();

sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒

}

catch  (Exception e)

{

}

}

public   static   void  main(String[] args)  throws  Exception

{

Thread threads[]  =   new  Thread[ 100 ];

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  建立100个线程

threads[i]  =   new  JoinThread();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  运行刚才建立的100个线程

threads[i].start();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  100个线程都执行完后继续

threads[i].join();

System.out.println( " n= "   +  JoinThread.n);

}

}

package  mythread;

public   class  JoinThread  extends  Thread

{

public   static int  n  =   0 ;

public static   synchronized   void  inc()

{

n ++ ;

}

public   void  run()

{

for  ( int  i  =   0 ; i  <   10 ; i ++ )

try

{

inc();  //  n = n + 1 改成了 inc();

sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒

}

catch  (Exception e)

{

}

}

public   static   void  main(String[] args)  throws  Exception

{

Thread threads[]  =   new  Thread[ 100 ];

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  建立100个线程

threads[i]  =   new  JoinThread();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  运行刚才建立的100个线程

threads[i].start();

for  ( int  i  =   0 ; i  <  threads.length; i ++ )

//  100个线程都执行完后继续

threads[i].join();

System.out.println( " n= "   +  JoinThread.n);

}

}

  上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++ 等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-09-09 14:34:15

Java中关键字volatile的作用的相关文章

show一下java中的volatile的作用

具体作用就不多讲了,大概就是保证可见性和防止重排序. 其实很早前就知道有这个关键字,试过很多次,总觉得加了和没加效果一样. 这次终于在阅读<java并发程序实践>(顺便义务广告一下,好书)的时候有了灵感,写了一段很普通的代码: Java代码 public class NoVisibility { private boolean ready=false; int num; private class ReaderThread extends Thread { public void run()

JAVA中toString方法的作用

以下是对在JAVA中toString方法的作用进行了详细的分析介绍,需要的朋友可以参考下   因为它是Object里面已经有了的方法,而所有类都是继承Object,所以"所有对象都有这个方法". 它通常只是为了方便输出,比如System.out.println(xx),括号里面的"xx"如果不是String类型的话,就自动调用xx的toString()方法 总而言之,它只是sun公司开发java的时候为了方便所有类的字符串操作而特意加入的一个方法  回答补充:写这个

java-求助,Java中对象序列化的作用是什么?

问题描述 求助,Java中对象序列化的作用是什么? Java中对象序列化的作用是什么?求大神相助,希望能全面一点哦.急求大神解答 解决方案 简单来说,对象像一颗树,里面有字段,如果字段是类型,那么它还有自己的字段. 但是网络传输.保存文件,都必须是线性的,连续的. 序列化就是把这棵树拍扁,成为一个序列的过程. 反序列化就是从一个序列中重新把这颗树拉回来的过程. 解决方案二: 把一个对象,转成流数据,对方根据这个数据还原成初始对象,适用于不同对象间的传输,通讯 解决方案三: 序列化多用于网络IO通

JAVA 类声明中关键字public的作用

      JAVA类声明中关键字public起到什么作用呢?如下Hello类的声明,按着字面的意思理解是:Hello类是公共的, 要求Hello类与类文件名必须保持一致,并且一个java文件中只能有一个public关键字声明的类. public class Hello{public static void main(String args[]){System.out.println("Hello JAVA!");} } 如果关键字public命名的类Hello与类文件名不一致,如下图

java中Class.forName的作用浅谈_java

Class.forName(xxx.xx.xx) 返回的是一个类 一.首先你要明白在java里面任何class都要装载在虚拟机上才能运行. 1. forName这句话就是装载类用的(new是根据加载到内存中的类创建一个实例,要分清楚).   至于什么时候用,可以考虑一下这个问题,给你一个字符串变量,它代表一个类的包名和类名,你怎么实例化它?     A a = (A)Class.forName("pacage.A").newInstance();        这和          

探讨C语言中关键字volatile的含义_C 语言

volatile 的意思是"易失的,易改变的".这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化.通常在程序中申明了一个变量时,编译器会尽量把它存放在通用寄存器中,例如ebx.当CPU把其值放到ebx中后就不会再关心对应内存中的值.若此时其他程序(例如内核程序或一个中断)修改了内存中它的值,ebx中的值并不会随之更新.为了解决这种情况就创建了volatile限定词,让代码在引用该变量时一定要从指定位置取得其值. 关键字volatile有什么含意?并给出三个不同的例

java中关键字public

使用public关键字时,它意味着紧随在public后面的成员声明适用于所有人,特别是适用于使用库的客户程序员.假定我们定义了一个名为dessert的包,其中包含下述单元(若执行该程序时遇到困难,请参考第3章3.1.2小节"赋值"):   //: Cookie.java // Creates a library package c05.dessert; public class Cookie { public Cookie() { System.out.println("Co

实例讲解Java中的接口的作用

接口的作用 接口的作用简单一点就是:接口是用来标记类的,不同的类属于不同的接口(通过向上转型),管理接口比管理各种各样的类方便多了,接口体现了抽象的观点,什么是抽象?抽象就是"抽去像的部分". 使用接口解决问题 问题:现在我们要写个连接数据库的类给用户使用,有两个函数:一个返回Connection对象,另一个是关闭数据库,close(),一般的解决方法是:给每个数据库写一个类,再根据用户使用的数据库决定使用具体的类. 好的,我们看看这样有什么不好之处: (1).首先每个类都要有重复的代

java中import和package作用与用法

有些人写了一阵子 Java,可是对於 Java 的 package 跟 import 还是不 太了解很多人以?樵?悸 .java 档案中的 import 会让编译器把所 import 的程式通通写到编译好的 .class 档案中,或是认? import 跟 C/C++ 的 include 相似,实际上,这是错误的观念. 让我们先了解一下,Java 的 package 到底有何用处.其实,package 名称就像是我们的姓,而 class 名称就像是我们的名字 .package 名称有很多 . 的