Java中volatile和synchronized 做用以及区别

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

=========================分割线1=================================

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

一个变量声明为volatile,就意味着这个变量是随时会被其余线程修改的,所以不能将它cache在线程memory中。如下例子展示了volatile的做用:
html

Java代码   收藏代码
  1. publicclass StoppableTask extends Thread {  java

  2. privatevolatileboolean pleaseStop;  安全

  3. publicvoid run() {  多线程

  4. while (!pleaseStop) {  app

  5. // do some stuff...jvm

  6.    }  ide

  7. }  性能

  8. publicvoid tellMeToStop() {  优化

  9.   pleaseStop = true;  this

  10.  }  

  11. }  


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

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

Reference:
http://www.javamex.com/tutorials/synchronization_volatile.shtml
http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
=========================分割线2=================================

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

Java代码   收藏代码
  1. int i1;              

  2. int geti1() {return i1;}  

  3. volatileint i2;  

  4. int geti2()  

  5. {return i2;}  

  6. int i3;                

  7. synchronizedint geti3() {return i3;}  

  8.   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就不是原子级别的:

Java代码   收藏代码
  1. package  mythread;  

  2. publicclass  JoinThread  extends  Thread  

  3. {  

  4. publicstaticvolatileint  n  =   0 ;  

  5. publicvoid  run()  

  6.    {  

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

  8. try

  9.        {  

  10.                n  =  n  +   1 ;  

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

  12.            }  

  13. catch  (Exception e)  

  14.            {  

  15.            }  

  16.    }  

  17. publicstaticvoid  main(String[] args)  throws  Exception  

  18.    {  

  19.        Thread threads[]  =   new  Thread[ 100 ];  

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

  21. //  创建100个线程

  22.            threads[i]  =   new  JoinThread();  

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

  24. //  运行刚才创建的100个线程

  25.            threads[i].start();  

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

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

  28.            threads[i].join();  

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

  30.    }  

  31. }  


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

n  =  n  +   1 ;
n ++ ;

     若是要想使这种状况变成原子操做,须要使用synchronized关键字,如上的代码能够改为以下的形式:

Java代码   收藏代码
  1. package  mythread;  

  2. publicclass  JoinThread  extends  Thread  

  3. {  

  4. publicstaticint  n  =   0 ;  

  5. publicstaticsynchronizedvoid  inc()  

  6.    {  

  7.        n ++ ;  

  8.    }  

  9. publicvoid  run()  

  10.    {  

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

  12. try

  13.            {  

  14.                inc();  //  n = n + 1 改为了 inc();

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

  16.            }  

  17. catch  (Exception e)  

  18.            {  

  19.            }  

  20.    }  

  21. publicstaticvoid  main(String[] args)  throws  Exception  

  22.    {  

  23.        Thread threads[]  =   new  Thread[ 100 ];  

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

  25. //  创建100个线程

  26.            threads[i]  =   new  JoinThread();  

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

  28. //  运行刚才创建的100个线程

  29.            threads[i].start();  

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

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

  32.            threads[i].join();  

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

  34.    }  

  35. }  


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