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

  Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了同步块synchronized和volatile关键字机制。
 
synchronized
  同步块大家都比较熟悉,通过synchronized关键字来实现,所有加上synchronized和块语句,在多线程访问的时候,同一时刻只能有一个线程能够用synchronized修饰的方法 或者代码块。
 
volatile
   用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值。volatile很容易被误用,用来进行原子性操作。
   下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一:
public class TestVolatile {
/*
* 执行环境——jdk版本:jdk1.6.0_31 ,内存 :3G cpu:x86 2.4G
*/
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() {
public void run() {
TestVolatile.inc();
}
}).start();
}
// 这里每次运行的值都有可能不同,可能为1000
System.out.println("运行结果:TestVolatile.count=" + TestVolatile.count);
}
}
// 运行结果:Counter.count=995
   实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count=995,可以看出,在多线程的环境下,Counter.count并没有期望结果是1000。   很多人以为,这个是多线程并发问题,只需要在变量count之前加上volatile就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望。
public class TestVolatile {
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() {
public void run() {
TestVolatile.inc();
}
}).start();
}
// 这里每次运行的值都有可能不同,可能为1000
System.out.println("运行结果:TestVolatile.count=" + TestVolatile.count);
}
}
//运行结果:Counter.count=992
  运行结果还是没有我们期望的1000,下面我们分析一下原因:
   在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这些交互:
              

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

  其中use and assign 可以多次出现。

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

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

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

  volatile是变量修饰符,而synchronized则作用于一段代码或方法。volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

  volatile关键字用于声明简单类型变量,如int、float、 boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。

  当变量的值由自身的上一个决定时,如n=n+1、n++ 等,就不是原子操作。只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。

volatile是否就是原子性/线程同步的的更多相关文章

  1. volatile 错误示范做线程同步 demo

    这里 http://hedengcheng.com/?p=725 有对volatile 非常详细的解释,看完之后,心里一惊,因为我刚好在一个项目里用了文中错误示范那种方式来做线程同步,场景如下: Th ...

  2. .net中的线程同步基础(搬运自CLR via C#)

    线程安全 此类型的所有公共静态(Visual Basic 中为 Shared)成员对多线程操作而言都是安全的.但不保证任何实例成员是线程安全的. 在MSDN上经常会看到这样一句话.表示如果程序中有n个 ...

  3. 剑指Offer——线程同步volatile与synchronized详解

    (转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...

  4. 线程同步Volatile与Synchronized(一)

    volatile 一.volatile修饰的变量具有内存可见性 volatile是变量修饰符,其修饰的变量具有内存可见性. 可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改 ...

  5. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

  6. Java线程(二):线程同步synchronized和volatile

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...

  7. Java线程-volatile不能保证原子性

    下面是一共通过volatile实现原子性的例子: 通过建立100个线程,计算number这个变量最后的结果. package com.Sychronized; public class Volatil ...

  8. 【C#进阶系列】28 基元线程同步构造

    多个线程同时访问共享数据时,线程同步能防止数据损坏.之所以要强调同时,是因为线程同步问题实际上就是计时问题. 不需要线程同步是最理想的情况,因为线程同步一般很繁琐,涉及到线程同步锁的获取和释放,容易遗 ...

  9. 第8章 用户模式下的线程同步(1)_Interlocked系列函数

    8.1 原子访问:Interlocked系列函数(Interlock英文为互锁的意思) (1)原子访问的原理 ①原子访问:指的是一线程在访问某个资源的同时,能够保证没有其他线程会在同一时刻访问该资源. ...

随机推荐

  1. Spark学习笔记2:RDD编程

    通过一个简单的单词计数的例子来开始介绍RDD编程. import org.apache.spark.{SparkConf, SparkContext} object word { def main(a ...

  2. 运行inetmgr提示“找不到文件”无法打开IIS管理器的解决办法

    运行inetmgr提示“找不到文件”无法打开IIS管理器的解决办法 不知道什么时候开始运行inetmgr就提示找不到文件了,本以为是IIS坏了,这两天发现IIS服务还是可以运行的,只是运行inetmg ...

  3. Java 泛型小结

    1.什么是泛型? 泛型(Generics )是把类型参数化,运用于类.接口.方法中,可以通过执行泛型类型调用 分配一个类型,将用分配的具体类型替换泛型类型.然后,所分配的类型将用于限制容器内使用的值, ...

  4. web基础 (一) http协议

    一.什么是web服务 浏览器与网页服务端发起的请求与回应(返回的是一堆字符串,浏览器去渲染生成页面!)都是 标准的CS模式 ---- bs模式:客户端用浏览器即可,服务端需要自己去写 http协议是建 ...

  5. mysq更新(六) 单表查询 多表查询

      本节重点: 单表查询 语法: 一.单表查询的语法 SELECT 字段1,字段2... FROM 表名 WHERE 条件 GROUP BY field HAVING 筛选 ORDER BY fiel ...

  6. 建造者模式(Builder)

    Separate the construction of a complex object form its representation so that the same construction ...

  7. linux命令--df/ps aux/netstat/hostname/tail

    查询文件系统 df -h 查询cpu使用情况 top 进程查看: ps aux | grep haproxy 端口查看: netstat -lntup 主机名查看 hostname 查看文件末尾字符串 ...

  8. linux7安装teamViewer

    参考网站:http://blog.sina.com.cn/s/blog_15308c8290102x72u.html 下载网站:https://www.teamviewer.com/zhCN/down ...

  9. NLP—WordNet——词与词之间的最小距离

    WordNet,是由Princeton 大学的心理学家,语言学家和计算机工程师联合设计的一种基于认知语言学的英语词典.它不是光把单词以字母顺序排列,而且按照单词的意义组成一个“单词的网络”.我们这次的 ...

  10. react部署之页面空白

    react部署之页面空白 问题:create-react-app build打包后,页面出现空白. 可能一: 控制台报错,js等文件找不到(404) 文件路径问题,只需修改package.json文件 ...