2018年7月13日

java中volatile关键字的作用与用法,讲的很透彻

volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。

volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。

也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。

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

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

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

复制代码
 1 public class StoppableTask extends Thread {
 2 
 3   private volatile boolean pleaseStop;
 4 
 5 
 6   public void run() {
 7 
 8     while (!pleaseStop) {
 9 
10      // do some stuff...
11 
12     }
13 
14  }
15 
16 
17   public void tellMeToStop() {
18 
19    pleaseStop = true;
20 
21   }
22 
23 }
复制代码

假如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代码: 

复制代码
1 int i1;             
2 int geti1() {return i1;} 
3 volatile int i2; 
4 int geti2()
5 {return i2;} 
6 int i3;              
7 synchronized int 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就不是原子级别的:

复制代码
 1 package  mythread;
 2 
 3 public   class  JoinThread  extends  Thread
 4 {
 5      public   static volatile int  n  =   0 ;
 6     public   void  run()
 7     {
 8          for  ( int  i  =   0 ; i  <   10 ; i ++ )
 9              try 
10         {
11                 n  =  n  +   1 ;
12                 sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒 
13 
14             }
15              catch  (Exception e)
16             {
17             }
18     }
19 
20      public   static   void  main(String[] args)  throws  Exception
21     {
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=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);
    }
} 
复制代码

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

本文转载:http://sakyone.iteye.com/blog/668091

posted @ 2018-07-13 00:27 蒿子 阅读(37129) 评论(2) 推荐(1)

实现Linux服务器资源实时监控

摘要: 公司的服务器需要实时监控,而且当用户空间已经满了,操作失败,或者出现程序Exception的时候就需要实时提醒,便于网管和程序员调式,这样就把这个实时监控系统分为了两部分, 第一部分:实时系统监控(cpu利用率,cpu温度,总内存大小,已使用内存大小) 第二部分:实时告警 由于无刷新实时性,所以只能 阅读全文

posted @ 2018-07-13 00:05 蒿子 阅读(693) 评论(0) 推荐(0)

2015年1月17日

SQL Server2008 程序设计 汇总 GROUP BY,WITH ROLLUP,WITH CUBE,GROUPING SETS(..)

摘要: --SQLServer2008程序设计汇总GROUPBY,WITHROLLUPWITHCUBEGROUPING SET(..)/*********************************************************************************主题:SQ... 阅读全文

posted @ 2015-01-17 21:40 蒿子 阅读(323) 评论(0) 推荐(0)

2014年12月23日

Android Fragment 基本介绍[转]

摘要: Fragment Android是在Android 3.0 (API level 11)开始引入Fragment的。 可以把Fragment想成Activity中的模块,这个模块有自己的布局,有自己的生命周期,单独处理自己的输入,在Activity运行的时候可以加载或者移除Fragment模块。... 阅读全文

posted @ 2014-12-23 14:25 蒿子 阅读(227) 评论(0) 推荐(0)

2014年10月17日

Android完全退出应用程序,完美解决方案

摘要: 最近公司工作不是很忙,就抽空研究了下Android的引导页,但是在写完引导页并且进入到住页面之后,在退出时,采用"再按一次退出"的方式去实现的,用的方式是杀掉进程跟exit,即:android.os.Process.killProcess(android.os.Process.myPid())或... 阅读全文

posted @ 2014-10-17 17:48 蒿子 阅读(2649) 评论(0) 推荐(0)

2014年1月22日

IIS ASP.NETWEB站点部署时遇到的问题记录

摘要: 最近由于工作的需要,需要自己部署一些ASP.NET站点,但中间出现了一点小小的问题。 由于自己才疏学浅,此问题折腾了我将近一个小时,最后还是百度出了解决这个问题的方法,先记录如下,仅供自己记忆用。 我的IIS的版本是7.5的,默认的.NET Framework版本是2.0,的而非4.0,所以就出现了采用.NET Framework 4.0的Web站点部署时报错。 windows7下发布web项目需要安装IIS,当安装完以后,web程序已经映射到了本地IIS上,运行出现如下错误提示处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“M... 阅读全文

posted @ 2014-01-22 17:50 蒿子 阅读(258) 评论(0) 推荐(0)

2013年12月27日

【转】单片机的简单学习心得

摘要: 此文为转载文章,仅供自己学习使用,先在此感谢各位单片机领域的大佬们的各种文章……要学习单片机,必须基本的模拟与数字电路的知识、基本的计算机理论知识与操作知识、明白C语言的基本规则与语句,同时还要有一本经典的单片机教材,下面是学习单片机的一些心得体会,希望对大家有所帮助。1、万事开头难、要勇敢迈出第一步。开始的时候,不要老是给自己找借口,说KEIL不会建项目啦、没有实验板啦之类的。遇到困难要一件件攻克,不会建项目,就先学它,这方面网上教程很多,随便找找看一下,做几次就懂了。然后可以参考别的人程序,抄过来也无所谓,写一个最简单的,让它运行起来,先培养一下自己的感觉,知道写程序是怎么一回事,无论写大 阅读全文

posted @ 2013-12-27 10:26 蒿子 阅读(617) 评论(0) 推荐(1)

< 2025年6月 >
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 1 2 3 4 5
6 7 8 9 10 11 12

导航

统计

点击右上角即可分享
微信分享提示