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

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

synchronized

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

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

volatile

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

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

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

  1. public class Counter {
  2.  
  3. public static int count = 0;
  4.  
  5. public static void inc() {
  6.  
  7. //这里延迟1毫秒,使得结果明显
  8. try {
  9. Thread.sleep(1);
  10. } catch (InterruptedException e) {
  11. }
  12.  
  13. count++;
  14. }
  15.  
  16. public static void main(String[] args) {
  17.  
  18. //同时启动1000个线程,去进行i++计算,看看实际结果
  19.  
  20. for (int i = 0; i < 1000; i++) {
  21. new Thread(new Runnable() {
  22. @Override
  23. public void run() {
  24. Counter.inc();
  25. }
  26. }).start();
  27. }
  28.  
  29. //这里每次运行的值都有可能不同,可能为1000
  30. System.out.println("运行结果:Counter.count=" + Counter.count);
  31. }
  32. }
  1.  
  1. 运行结果:Counter.count=995
  1. 实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count=995,可以看出,在多线程的环境下,Counter.count并没有期望结果是1000
  1.  
  1. 很多人以为,这个是多线程并发问题,只需要在变量count之前加上volatile就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望
  1. public class Counter {
  2.  
  3. public volatile static int count = 0;
  4.  
  5. public static void inc() {
  6.  
  7. //这里延迟1毫秒,使得结果明显
  8. try {
  9. Thread.sleep(1);
  10. } catch (InterruptedException e) {
  11. }
  12.  
  13. count++;
  14. }
  15.  
  16. public static void main(String[] args) {
  17.  
  18. //同时启动1000个线程,去进行i++计算,看看实际结果
  19.  
  20. for (int i = 0; i < 1000; i++) {
  21. new Thread(new Runnable() {
  22. @Override
  23. public void run() {
  24. Counter.inc();
  25. }
  26. }).start();
  27. }
  28.  
  29. //这里每次运行的值都有可能不同,可能为1000
  30. System.out.println("运行结果:Counter.count=" + Counter.count);
  31. }
  32. }

运行结果:Counter.count=992

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

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

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

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

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

描述这写交互

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关键字修改之后,还是会存在并发的情况。

volatile关键字并不能作为线程计数器的更多相关文章

  1. 面试时通过volatile关键字,全面展示线程内存模型的能力

    面试时,面试官经常会通过volatile关键字来考核候选人在多线程方面的能力,一旦被问题此类问题,大家可以通过如下的步骤全面这方面的能力.     1 首先通过内存模型说明volatile关键字的作用 ...

  2. 【线程】Volatile关键字

    Volatile变量具有 synchronized 的可见性特性,但是不具备原子特性.这就是说线程能够自动发现 volatile变量的最新值.Volatile变量可用于提供线程安全,但是只能应用于非常 ...

  3. 线程安全(上)--彻底搞懂volatile关键字

    对于volatile这个关键字,相信很多朋友都听说过,甚至使用过,这个关键字虽然字面上理解起来比较简单,但是要用好起来却不是一件容易的事.这篇文章将从多个方面来讲解volatile,让你对它更加理解. ...

  4. java内存模型(线程,volatile关键字和sychronized关键字)

    volatile关键字 用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况. ...

  5. 深入解析Java中volatile关键字的作用

    转(http://m.jb51.net/article/41185.htm)Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制 在java线 ...

  6. Java中volatile关键字解析

    一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存 ...

  7. Java并发编程(六)volatile关键字解析

    由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...

  8. Java并发编程之三:volatile关键字解析 转载

    目录: <Java并发编程之三:volatile关键字解析 转载> <Synchronized之一:基本使用>   volatile这个关键字可能很多朋友都听说过,或许也都用过 ...

  9. 13、Java并发性和多线程-Java Volatile关键字

    以下内容转自http://tutorials.jenkov.com/java-concurrency/volatile.html(使用谷歌翻译): Java volatile关键字用于将Java变量标 ...

随机推荐

  1. GetKeyboardType获取键盘类型(通过键盘可初步判断用户使用的是台式电脑还是笔记本电脑)

    函数功能:该函数获取系统当前键盘的信息. int WINAPI GetKeyboardType( __in int nTypeFlag ); 参数说明:nTypeFlag:指定要获取的键盘信息的类型, ...

  2. 9月10日,美团网2014校招研发笔试哈尔滨站 1、链表翻转。给出一个链表和一个数k,比如链表1→2→3→4→5→6,k=2,则翻转后2→1→4→3→6→5,若k=3,翻转后3→2→1→6→5→4,若k=4,翻转后4→3→2→1→5→6,用程序实现

    // reverselink.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" struct Node{ int num; struct No ...

  3. Generate Ubuntu Install Media On Mac

    Opps, my computer system was broken again... Let's repire it. Introduction The system of my PC is br ...

  4. Windows服务安装与卸载

    Windows服务安装与卸载,使用到了InstallUtil.exe 安装: c: cd "C:\Windows\Microsoft.NET\Framework\v4.0.30319&quo ...

  5. C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0

    C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0

  6. python学习笔记之装饰器、递归、算法(第四天)

    参考老师的博客: 金角:http://www.cnblogs.com/alex3714/articles/5161349.html 银角:http://www.cnblogs.com/wupeiqi/ ...

  7. Good Bye 2015 D. New Year and Ancient Prophecy

    D. New Year and Ancient Prophecy time limit per test 2.5 seconds memory limit per test 512 megabytes ...

  8. 笔记--MySQL相关操作

    一  登录数据库 1 用户无密码: mysql -uroot -p mysql-> 2 用户有密码: MySQL -root -p[passwd] mysql-> 二  创建数据库: 查询 ...

  9. Dynamics AX 2012 R2 安装Reporting Services 扩展

    今天Reinhard在VS中部署SSRS报表时,接到以下错误: 部署因错误而被取消.在报表服务器上,验证:-SQL Server Reporting Services 服务是否正在运行. 接着,Rei ...

  10. PHP命令行脚本接收传入参数的三种方式

    通常PHP都做http方式请求了,可以使用GET or POST方式接收参数,有些时候需要在shell命令下把PHP当作脚本执行,比如定时任务.这就涉及到在shell命令下如何给php传参的问题,通常 ...