在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. Flex 学习

    Flex案例一: <html> <head> <meta http-equiv="Content-Type" content="text/h ...

  2. select count(*) as total from(select count(*) from tab_cb_casim group by `card_no`) as cai;

    子查询必须加一个别名才能执行!!

  3. tornado.gen 模块解析

    转自:http://strawhatfy.github.io/2015/07/22/Tornado.gen/ 引言 注:正文中引用的 Tornado 代码除特别说明外,都默认引用自 Tornado 4 ...

  4. Docker集群管理(一)—— 基础docker+swarm+shipyard

    目的 学习docker的集群管理,摸索出高可用的docker微服务架构方案.本篇文章只初步的了解下swarm(docker新版已集成了swarm)的使用,了解docker的发现服务的基础方法(dock ...

  5. var与dynamic

    var与dynamic 如果你用MVC写过程序,那么你应该知道ViewBag这个用于前后台的数据传递工具,那么你是否对ViewBag的用法感到过疑惑呢? ViewBag.Mode1l=new obje ...

  6. 代码生成器 CodeSmith 的使用(一)

    由于在项目中经常要会用到数据库的 CRUD 操作(增.删.改.查),而且还使用的是orm 框架将数据库表名和表中的的字段映射成相应的类属性.如果把大量的时间用到手工输入数据库表中的字段,除了能练习打字 ...

  7. 一、探索 Android Studio

    探索 Android Studio 本文内容 项目结构 界面 Gradle 构建系统 调试和分析工具 Android Studio 是基于 IntelliJ IDEA 的官方 Android 应用开发 ...

  8. IP分组交付和转发

    1:交付 网络层监视底层物理网络对分组的处理过程叫做交付,分为直接交付和间接交付 1.1:直接交付 直接交付时,分组的终点是一台与交付着连接在同一个网络上的主机,发生在俩种情况下,分组的源点和终点都在 ...

  9. sqoop1 使用测试

    hive导入数据到mysql最简单的方式就是从hdfs直接读取hive表文件导入mysql,当然这需要知道数据表保存的目录 如果能直接从表到表的导入,无需路径,当然是最好了 1.需要下载合适的hive ...

  10. 程序员必看:给你一份详细的Spring Boot知识清单

    在过去两三年的Spring生态圈,最让人兴奋的莫过于Spring Boot框架.或许从命名上就能看出这个框架的设计初衷:快速的启动Spring应用.因而Spring Boot应用本质上就是一个基于Sp ...