文档章节

volatile(不能保证原子性)

one_pieceD
 one_pieceD
发布于 2016/04/20 14:15
字数 1239
阅读 80
收藏 0

java中volatile关键字的含义

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。

Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。

 

synchronized 

同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用

synchronized 修饰的方法 或者 代码块。

 

volatile

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。

 

下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一

 

执行环境——jdk版本:jdk1.6.0_31 ,内存 :3G   cpu:x86 2.4G

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
31
32
public   class   Counter {
 
     public   static   int   count = 0 ;
 
     public   static   void   inc() {
 
         //这里延迟1毫秒,使得结果明显
         try   {
             Thread.sleep( 1 );
         } catch   (InterruptedException e) {
         }
 
         count++;
     }
 
     public   static   void   main(String[] args) {
 
         //同时启动1000个线程,去进行i++计算,看看实际结果
 
         for   ( int   i = 0 ; i < 1000 ; i++) {
             new   Thread( new   Runnable() {
                 @Override
                 public   void   run() {
                     Counter.inc();
                 }
             }).start();
         }
 
         //这里每次运行的值都有可能不同,可能为1000
         System.out.println( "运行结果:Counter.count="   + Counter.count);
     }
}
1
1
运行结果:Counter.count= 995
1
实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count= 995 ,可以看出,在多线程的环境下,Counter.count并没有期望结果是 1000
1
1
很多人以为,这个是多线程并发问题,只需要在变量count之前加上 volatile 就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望
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
31
32
public   class   Counter {
 
     public   volatile   static   int   count = 0 ;
 
     public   static   void   inc() {
 
         //这里延迟1毫秒,使得结果明显
         try   {
             Thread.sleep( 1 );
         } catch   (InterruptedException e) {
         }
 
         count++;
     }
 
     public   static   void   main(String[] args) {
 
         //同时启动1000个线程,去进行i++计算,看看实际结果
 
         for   ( int   i = 0 ; i < 1000 ; i++) {
             new   Thread( new   Runnable() {
                 @Override
                 public   void   run() {
                     Counter.inc();
                 }
             }).start();
         }
 
         //这里每次运行的值都有可能不同,可能为1000
         System.out.println( "运行结果:Counter.count="   + Counter.count);
     }
}

运行结果:Counter.count=992

运行结果还是没有我们期望的1000,下面我们分析一下原因

 

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图

描述这写交互

 

java volatile1

 

 

read and load 从主存复制变量到当前工作内存
use and assign  执行代码,改变共享变量值 
store and write 用工作内存数据刷新主存相关内容

其中use and assign 可以多次出现

但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样

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

例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值

在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6

线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6

导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。

本文转载自:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

上一篇: ArrayList源码
one_pieceD
粉丝 0
博文 14
码字总数 9119
作品 0
杭州
私信 提问
简单说说你对Java内存模型的原子性的了解吧?

Java内存模型保证non-long、non-double类型外的其他任意类型的访问都是原子性的。同时volatile long、volatile double访问也是原子性的。 参考资料: 同步和Java内存模型 (二)原子性 除了lon...

nj-zhangmq
2016/12/09
27
0
Java并发:volatile的实现原理

synchronized 是一个重量级的锁, volatile 通常被比喻成轻量级的 synchronized volatile 是一个变量修饰符,只能用来修饰变量。 volatile写:当写一个volatile变量时,JMM会把该线程对应的本...

小刀爱编程
2018/11/02
18
0
volatile关键字解析~高级java必问

昨天我介绍了原子性、可见性、有序性的概念,那么今天就来见识下这些概念的主角-volatile关键字。 volatile基本介绍 volatile可以看成是synchronized的一种轻量级的实现,但volatile并不能完...

java技术栈
2017/08/13
0
0
「原创」Java并发编程系列05 深入理解volatile

  思维导图      写在前面   本篇文章介绍并发编程中常用的volatile关键字。主要介绍两方面内容:   volatile有哪些特性,可以用来做什么?   volatile实现原理。   1. 保证可...

java进阶架构师
10/14
0
0
c/c++语言中的volatile 保证读写线程安全的条件

背景: 关于这个问题之前讨论了很多, 比如: C++多线程有必要加volatile么? 多线程编程中什么情况下需要加 volatile? 文中大多认为, volatile不能保证线程安全, 不是标准操作, 不要使用. 这样...

harleyliao
05/15
6
0

没有更多内容

加载失败,请刷新页面

加载更多

如何有效地计算JavaScript中对象的键/属性数量?

计算对象的键/属性数的最快方法是什么? 是否可以在不迭代对象的情况下执行此操作? 即不做 var count = 0;for (k in myobj) if (myobj.hasOwnProperty(k)) count++; (Firefox确实提供了一...

技术盛宴
17分钟前
2
0
百度网址安全中心拦截解除的办法分享

临近2019年底,客户的公司网站被百度网址安全中心拦截了,公司网站彻底打不开了,影响范围很大,于是通过朋友介绍找到我们SINE安全公司寻求帮忙解封,关于如何解除百度的安全拦截提示,下面就...

网站安全
29分钟前
3
0
Tomcat8源码分析-启动流程-start方法

上一篇:Tomcat8源码分析-启动流程-load方法 前面讲了启动流程中的Catalina.load,进一步调用绝大部分组建的init操作,主要完成对server.xml解析,并根据解析的结果结合设置的Rule(规则)构造...

特拉仔
37分钟前
6
0
Xamarin.FormsShell基础教程(7)Shell项目关于页面的介绍

Xamarin.FormsShell基础教程(7)Shell项目关于页面的介绍 轻拍标签栏中的About标签,进入关于页面,如图1.8和图1.9所示。它是对应用程序介绍的页面。 该页面源自Views文件夹中的AboutPage.x...

大学霸
43分钟前
3
0
一步一步理解Impala query profile(一)

很多Impala用户不知道如何阅读Impala query profile来了解一个查询背后正在执行的操作,从而在此基础上对查询进行调优以充分发挥查询的性能。因此我想写一篇简单的文章来分享我的经验,并希望...

九州暮云
45分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部