热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java并发关键字_Java并发关键字volatile

Java并发关键字volatileauthorixenosvolatile只是保证了共享变量的可见性,不保证同步操作的原子性同步块和volatile关键字机制sync

Java 并发 关键字volatile

@author ixenos

volatile只是保证了共享变量的可见性,不保证同步操作的原子性

同步块 和 volatile 关键字机制

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

volatile  用volatile修饰的变量,线程在每次刚使用变量的时候,都会读取变量修改后的最新的值的副本到线程栈中。volatile很容易被误用来进行原子性操作,因为线程对其操作只是副本上,并不会实时更改共享内存中的变量值。

VM为什么要使用副本呢,在副本上操作时可以进行读写操作的重排序优化,能提升运行效率,而是用volatile将禁止副本的使用,而且在volatile变量周围限制重排序操作,具体分析请看:深入理解Java内存模型(四)——volatile

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

1 public classCounter {2

3 public static int count = 0;4

5 public static voidinc() {6

7 //这里延迟1毫秒,使得结果明显

8 try{9 Thread.sleep(1);10 } catch(InterruptedException e) {11 }12

13 count++;14 }15

16 public static voidmain(String[] args) {17

18 //同时启动1000个线程,去进行i++计算,看看实际结果

19

20 for (int i &#61; 0; i <1000; i&#43;&#43;) {21 new Thread(newRunnable() {22 &#64;Override23 public voidrun() {24 Counter.inc();25 }26 }).start();27 }28

29 //这里每次运行的值都有可能不同,可能为1000

30 System.out.println("运行结果:Counter.count&#61;" &#43;Counter.count);31 }32 }33

34 /*运行结果:Counter.count&#61;995*/

实际运算结果每次可能都不一样&#xff0c;本机的结果为&#xff1a;运行结果:Counter.count&#61;995&#xff0c;可以看出&#xff0c;在多线程的环境下&#xff0c;Counter.count并没有期望结果是1000

很多人以为&#xff0c;这个是多线程并发问题&#xff0c;只需要在变量count之前加上volatile就可以避免这个问题&#xff0c;那我们在修改代码看看&#xff0c;看看结果是不是符合我们的期望

1 public classCounter {2

3 public volatile static int count &#61; 0;4

5 public static voidinc() {6

7 //这里延迟1毫秒&#xff0c;使得结果明显

8 try{9 Thread.sleep(1);10 } catch(InterruptedException e) {11 }12

13 count&#43;&#43;;14 }15

16 public static voidmain(String[] args) {17

18 //同时启动1000个线程&#xff0c;去进行i&#43;&#43;计算&#xff0c;看看实际结果

19

20 for (int i &#61; 0; i <1000; i&#43;&#43;) {21 new Thread(newRunnable() {22 &#64;Override23 public voidrun() {24 Counter.inc();25 }26 }).start();27 }28

29 //这里每次运行的值都有可能不同,可能为1000

30 System.out.println("运行结果:Counter.count&#61;" &#43;Counter.count);31 }32 }33

34 /*运行结果:Counter.count&#61;992*/

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

在 java 垃圾回收整理一文中&#xff0c;描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈&#xff0c;每一个线程运行时都有一个线程栈&#xff0c;线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候&#xff0c;首先通过对象的引用找到对应在堆内存的变量的值&#xff0c;然后把堆内存变量的具体值load到线程本地内存中&#xff0c;建立一个变量副本&#xff0c;之后线程就不再和对象在堆内存变量值有任何关系&#xff0c;而是直接修改副本变量的值&#xff0c;在修改完之后的某一个时刻(线程退出之前)&#xff0c;自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

下面一幅图描述这写交互:

22cbe2ef3bc9e81d917ba6c533e41ece.png

read and load 从主存复制变量到当前工作内存

use and assign  执行代码&#xff0c;改变共享变量值

store and write 用工作内存数据刷新主存相关内容

其中use and assign 可以多次出现

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

总之&#xff0c;volatile使线程直接和共享内存的数据交互&#xff0c;并阻止VM的重排序优化

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



推荐阅读
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
author-avatar
韵公举_R
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有