JAVA Synchronized (三) volatile 与 synchronized 的比较
一,volatile关键字的可见性
要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下:
从图中可以看出:
①每个线程都有一个自己的本地内存空间--线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作
②对该变量操作完后,在某个时间再把变量刷新回主内存
关于JAVA内存模型,更详细的可参考: 深入理解Java内存模型(一)——基础
因此,就存在内存可见性问题,看一个示例程序:(摘自书上)

- 1 public class RunThread extends Thread {
- 2
- 3 private boolean isRunning = true;
- 4
- 5 public boolean isRunning() {
- 6 return isRunning;
- 7 }
- 8
- 9 public void setRunning(boolean isRunning) {
- 10 this.isRunning = isRunning;
- 11 }
- 12
- 13 @Override
- 14 public void run() {
- 15 System.out.println("进入到run方法中了");
- 16 while (isRunning == true) {
- 17 }
- 18 System.out.println("线程执行完成了");
- 19 }
- 20 }
- 21
- 22 public class Run {
- 23 public static void main(String[] args) {
- 24 try {
- 25 RunThread thread = new RunThread();
- 26 thread.start();
- 27 Thread.sleep(1000);
- 28 thread.setRunning(false);
- 29 } catch (InterruptedException e) {
- 30 e.printStackTrace();
- 31 }
- 32 }
- 33 }

Run.java 第28行,main线程 将启动的线程RunThread中的共享变量设置为false,从而想让RunThread.java 第14行中的while循环结束。
如果,我们使用JVM -server参数执行该程序时,RunThread线程并不会终止!从而出现了死循环!!
原因分析:
现在有两个线程,一个是main线程,另一个是RunThread。它们都试图修改 第三行的 isRunning变量。按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。
而在JVM 设置成 -server模式运行程序时,线程会一直在私有堆栈中读取isRunning变量。因此,RunThread线程无法读到main线程改变的isRunning变量
从而出现了死循环,导致RunThread无法终止。这种情形,在《Effective JAVA》中,将之称为“活性失败”
解决方法,在第三行代码处用 volatile 关键字修饰即可。这里,它强制线程从主内存中取 volatile修饰的变量。
- volatile private boolean isRunning = true;
扩展一下,当多个线程之间需要根据某个条件确定 哪个线程可以执行时,要确保这个条件在 线程 之间是可见的。因此,可以用volatile修饰。
综上,volatile关键字的作用是:使变量在多个线程间可见(可见性)
二,volatile关键字的非原子性
所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。
比如,变量的自增操作 i++,分三个步骤:
①从内存中读取出变量 i 的值
②将 i 的值加1
③将 加1 后的值写回内存
这说明 i++ 并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第②时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。
关于volatile的非原子性,看个示例:

- 1 public class MyThread extends Thread {
- 2 public volatile static int count;
- 3
- 4 private static void addCount() {
- 5 for (int i = 0; i < 100; i++) {
- 6 count++;
- 7 }
- 8 System.out.println("count=" + count);
- 9 }
- 10
- 11 @Override
- 12 public void run() {
- 13 addCount();
- 14 }
- 15 }
- 16
- 17 public class Run {
- 18 public static void main(String[] args) {
- 19 MyThread[] mythreadArray = new MyThread[100];
- 20 for (int i = 0; i < 100; i++) {
- 21 mythreadArray[i] = new MyThread();
- 22 }
- 23
- 24 for (int i = 0; i < 100; i++) {
- 25 mythreadArray[i].start();
- 26 }
- 27 }
- 28 }

MyThread类第2行,count变量使用volatile修饰
Run.java 第20行 for循环中创建了100个线程,第25行将这100个线程启动去执行 addCount(),每个线程执行100次加1
期望的正确的结果应该是 100*100=10000,但是,实际上count并没有达到10000
原因是:volatile修饰的变量并不保证对它的操作(自增)具有原子性。(对于自增操作,可以使用JAVA的原子类AutoicInteger类保证原子自增)
比如,假设 i 自增到 5,线程A从主内存中读取i,值为5,将它存储到自己的线程空间中,执行加1操作,值为6。此时,CPU切换到线程B执行,从主从内存中读取变量i的值。由于线程A还没有来得及将加1后的结果写回到主内存,线程B就已经从主内存中读取了i,因此,线程B读到的变量 i 值还是5
相当于线程B读取的是已经过时的数据了,从而导致线程不安全性。这种情形在《Effective JAVA》中称之为“安全性失败”
综上,仅靠volatile不能保证线程的安全性。(原子性)
此外,volatile关键字修饰的变量不会被指令重排序优化。这里以《深入理解JAVA虚拟机》中一个例子来说明下自己的理解:
线程A执行的操作如下:

- Map configOptions ;
- char[] configText;
- volatile boolean initialized = false;
- //线程A首先从文件中读取配置信息,调用process...处理配置信息,处理完成了将initialized 设置为true
- configOptions = new HashMap();
- configText = readConfigFile(fileName);
- processConfig(configText, configOptions);//负责将配置信息configOptions 成功初始化
- initialized = true;

线程B等待线程A把配置信息初始化成功后,使用配置信息去干活.....线程B执行的操作如下:

- while(!initialized)
- {
- sleep();
- }
- //使用配置信息干活
- doSomethingWithConfig();

如果initialized变量不用 volatile 修饰,在线程A执行的代码中就有可能指令重排序。
即:线程A执行的代码中的最后一行:initialized = true 重排序到了 processConfig方法调用的前面执行了,这就意味着:配置信息还未成功初始化,但是initialized变量已经被设置成true了。那么就导致 线程B的while循环“提前”跳出,拿着一个还未成功初始化的配置信息去干活(doSomethingWithConfig方法)。。。。
因此,initialized 变量就必须得用 volatile修饰。这样,就不会发生指令重排序,也即:只有当配置信息被线程A成功初始化之后,initialized 变量才会初始化为true。综上,volatile 修饰的变量会禁止指令重排序(有序性)
三,volatile 与 synchronized 的比较
volatile主要用在多个线程感知实例变量被更改了场合,从而使得各个线程获得最新的值。它强制线程每次从主内存中讲到变量,而不是从线程的私有内存中读取变量,从而保证了数据的可见性。
关于synchronized,可参考:JAVA多线程之Synchronized关键字--对象锁的特点
比较:
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
四,线程安全性
线程安全性包括两个方面,①可见性。②原子性。
从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。
JAVA多线程之当一个线程在执行死循环时会影响另外一个线程吗?
JAVA Synchronized (三) volatile 与 synchronized 的比较的更多相关文章
- java多线程中 volatile与synchronized的区别-阿里面试
volatile 与 synchronized 的比较(阿里面试官问的问题) ①volatile轻量级,只能修饰变量.synchronized重量级,还可修饰方法 ②volatile只能保证数据的可见 ...
- java中的volatile和synchronized
关于volatile和同步相关的东西,网上有太多错误和解释不清的东西, 所以查阅相关书籍和文章后总结如下, 如果还是也存在不正确的内容,请一定要指出来, 以免误人子弟:) 1. 原子性与可视性 原子性 ...
- Java中的Volatile和synchronized的区别
Synchronized和Volatile四个不同点: 1.粒度不同,前者锁对象和类 ,后者针对变量2.syn阻塞,volatile线程不阻塞3.syn保证三大特性,volatile不保证原子性4.s ...
- Java 并发编程——volatile与synchronized
一.Java并发基础 多线程的优点 资源利用率更好 程序设计在某些情况下更简单 程序响应更快 这一点可能对于做客户端开发的更加清楚,一般的UI操作都需要开启一个子线程去完成某个任务,否者会容易导致客户 ...
- java多线程(三)——锁机制synchronized(同步语句块)
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法之行一个长时间的任务,那么B线程必须等待比较长的时间,在这样的情况下可以使用synchronized同步语句快来解 ...
- 从JAVA看C#中volatile和synchronized关键字的作用
最近一直在想C#中 volatile关键字到底是用来干什么的?查了很多.NET的文章都是说用volatile修饰的变量可以让多线程同时修改,这是什么鬼... 然后查到了下面这篇JAVA中关于volat ...
- Java内存可见性volatile
概述 JMM规范指出,每一个线程都有自己的工作内存(working memory),当变量的值发生变化时,先更新自己的工作内存,然后再拷贝到主存(main memory),这样其他线程就能读取到更新后 ...
- java面试记录二:spring加载流程、springmvc请求流程、spring事务失效、synchronized和volatile、JMM和JVM模型、二分查找的实现、垃圾收集器、控制台顺序打印ABC的三种线程实现
注:部分答案引用网络文章 简答题 1.Spring项目启动后的加载流程 (1)使用spring框架的web项目,在tomcat下,是根据web.xml来启动的.web.xml中负责配置启动spring ...
- Java 线程 — synchronized、volatile、锁
线程同步基础 synchronized 和volatile是Java线程同步的基础. synchronized 将临界区的内容上锁,同一时刻只有一个进程能访问该临界区代码 使用的是内置锁,锁一个时刻只 ...
随机推荐
- codevs——1049 棋盘染色
1049 棋盘染色 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description 有一个5×5的棋盘,上面有一 ...
- Java集合——遍历集合元素并修改
Java集合——遍历集合元素并修改 摘要:本文主要总结了遍历集合的方式,以及在遍历时修改集合要注意的问题. 遍历Collection 对List和Set的遍历,有四种方式,下面以ArrayList为例 ...
- android 按两次物理返回键退出程序
<?xml version="1.0" encoding="utf-8"?> <!-- 定义当前布局的基本LinearLayout --> ...
- 前端高频面试题 CSS篇
通过从网上看到的面经,总结的一些高频的前端CSS面试题,有很多不会,于是找资料,并通过代码实现,自己提供了一些参考答案. 目录 1.怎样会产生浮动? 2.如何清除浮动? 3.盒子模型,以及IE和其他浏 ...
- mysql 安装与配置、使用
http://www.cnblogs.com/shenliang123/p/3203546.html CentOs中mysql的安装与配置 在linux中安装数据库首选MySQL,Mysql数据库的第 ...
- find the longest of the shortest (hdu 1595 SPFA+枚举)
find the longest of the shortest Time Limit: 1000/5000 MS (Java/Others) Memory Limit: 32768/32768 ...
- error C2065: 'CArchiveStream' : undeclared identifier
release:模式下 问题: 在导入JPEG文件时要使用到 CArchiveStream类 但是编译的时候会出现 'CArchiveStream' : undeclared ide ...
- AppFuse 3的乱码问题
书接上回:AppFuse 3常见问题与解决方法,一个新问题:乱码! 在3.0.0版本号下,运行mvn appfuse:full-source命令后使用mvn jetty:run启动工程,页面上就出现了 ...
- git常用知识笔记
学习资料: http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 http://codi ...
- block-循环引用
在ARC机制下,app的内存管理由操作系统进行管理,不须要程序猿手动的管理内存,方便了开发.虽然,自己主动释放内存非常方便.可是并不是绝对安全,绝对不会产生内存泄露. 大部分导致iOS对象无法按预期释 ...