volatile变量能保证线程安全性吗?为什么?
1. volatile是什么?
在谈及线程安全时,常会说到一个变量——volatile。在《Java并发编程实战》一书中是这么定义volatile的——“Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程”。这句话说明了两点:①volatile变量是一种同步机制;②volatile能够确保可见性。这两点和我们探讨“volatile变量是否能够保证线程安全性”息息相关。
2. volatile变量能确保线程安全性吗?为什么?
什么是同步机制?在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步。也就是说,同步机制即为对共享资源的一种制约。那么问题来了:volatile这种“稍弱的同步机制”是怎么制约各个进程对共享资源的访问的呢?答案就在“volatile能够确保可见性”中。
2.1 可见性
volatile能够保证字段的可见性:volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
可见性和“线程如何对变量进行操作(取值、赋值等)”有关系:
我们要先明确一个定律:线程对变量的所有操作(取值、赋值等)都必须在工作内存(各线程独立拥有)中进行,而不能直接读写内存中的变量,各工作内存间也不能相互访问。对于volatile变量来说,由于它特殊的操作顺序性规定,看起来如同操作主内存一般,但实际上 volatile变量也是遵循这一定律的。
关于主存与工作内存之间具体的交互协议(即一个变量如何从主存拷贝到工作内存、如何从工作内存同步到主存等实现细节),Java内存模型中定义了以下八种操作来完成:
lock:(锁定),unlock(解锁),read(读取),load(载入),use(试用), assign(赋值),store(存储),write(写入)。
volatile 对这八种操作有着两个特殊的限定,正因为有这些限定才让volatile修饰的变量有可见性以及可以禁止指令重排序 :
① use动作之前必须要有read和load动作, 这三个动作必须是连续出现的。【表示:每次工作内存要使用volatile变量之前必须去主存中拿取最新的volatile变量】
② assign动作之后必须跟着store和write动作,这三个动作必须是连续出现的。【表示: 每次工作内存改变了volatile变量的值,就必须把该值写回到主存中】
有以上两条规则就能保证每个线程每次去拿volatile变量的时候,那个变量肯定是最新的, 其实也就相当于好多个线程用的是同一个内存,无工作内存和主存之分。而操作没有用volatile修饰的变量则不能保证每次都能获取到最新的变量值。
2.2 所以volatile究竟能否保证线程安全性?
答:在某些特定的情况下能。那到底什么是什么能,什么时候又不能了呢?我们继续往下看。
(1)volatile能保证线程安全的情况
要使 volatile 变量提供理想的线程安全性,必须同时满足两个条件:①对变量的写操作不依赖于当前值。②该变量没有包含在具有其他变量的不变式中。
实际上,这两个条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。大多数编程情形都会与这两个条件的其中之一冲突(如:“若没有则添加”、“若相等则移出”的复合操作等复合操作都是与①或②相冲突的),使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。
(2)volatile不能保证线程安全的情况
除了(1)中提到的能够使volatile发挥保证线程安全性的情况,其他情况下volatile并不能保证线程安全问题,因为volatile并不能保证变量操作的原子性。
我们先以 i++( i++ 是非原子操作)为例:
private volatile int i = 0,两个线程同时执行 i++,
此时是两个线程同时从主内存中拿到 i 的最新值 0 ,并且同时对 i 进行 +1 操作并将和赋值回 i,最后同时将 +1 后的 i 值写回主内存中,最终 i == 1,很明显结果是错的。
下面我们再通过更详细的代码来验证“即使变量用了volatile来修饰,才进行非原子操作时依旧会出现线程安全问题”:
class Window implements Runnable {
private volatile int ticket = 100; public void run() {
for (;;) {
//通过下面的①②两个步骤我们可以发现:对一个共享资源可以多个线程同时进行修改,自然就会有线程安全问题。
if (ticket > 0) {
try {
Thread.sleep(100);//①多个线程同时判断到“ticket>0”,然后挂起了
} catch (InterruptedException e) {
e.printStackTrace();
}
//②多个线程同时醒来,同时进行“ticket--”操作:
System.out.println(Thread.currentThread().getName() + ":" + ticket--);
} else {
break;
}
}
}
}
public class A03UseVolatileIsNotThreadSafe {
public static void main(String[] args) {
Window w = new Window(); Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w); t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3"); t1.start();
t2.start();
t3.start();
}
}
测试结果:(1)出现了大量的重复数字; (2)最后还输出了 “-1”;==》说明变量即使用volatile修饰了但依旧出现了线程安全问题。
代码解析:
出现问题(1)的原因:线程存在“先检查后执行”的竞态条件。可能有两个线程同时拥有CPU的执行权(机器是双核的),它们判断到做“if (ticket > 0)”,并同时做“ticket--”操作。
出现问题(2)的原因:
①当ticket==1时,两个或多个线程同时通过了“if (ticket > 0)”的判断,并进入了判断框中去执行代码;
②然后它们执行到“Thread.sleep(100);”就睡了;
③睡醒后总有一个线程会先抢到cup的执行权,然后执行“ticket--”操作,并将最新的ticket数值推送告知到每个线程;
④此时那些在判断框中的其他的线程并不会再次做“if (ticket > 0)”的判断,而是直接拿最新的ticket并做“ticket--”操作。
就算线程在“ticket--”之前每次都做“if (ticket > 0)”的判断,也依旧会有线程安全问题,因为又可能出现①那种同时通过判断的状态。
// volatile的典型用法:检查某个状态标记,以判断是否退出循环。
volatile boolean asleep;
……
while( !asleep){
countSomeSheep();
}
3. volatile的典型用法:检查某个状态标记,以判断是否退出循环。
volatile boolean asleep;
……
while( !asleep){
countSomeSheep();
}
4. 总结:因为volatile不能保证变量操作的原子性,所以试图通过volatile来保证线程安全性是不靠谱的。
volatile变量能保证线程安全性吗?为什么?的更多相关文章
- spring singleton实例中的变量怎么保证线程安全
pring中管理的bean实例默认情况下是单例的[sigleton类型],就还有prototype类型按其作用域来讲有sigleton,prototype,request,session,global ...
- java中volatile不能保证线程安全
今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而 ...
- 在JAVA中ArrayList如何保证线程安全
[b]保证线程安全的三种方法:[/b]不要跨线程访问共享变量使共享变量是final类型的将共享变量的操作加上同步一开始就将类设计成线程安全的, 比在后期重新修复它,更容易.编写多线程程序, 首先保证它 ...
- 《Java并发编程实战》第二章 线程安全性 读书笔记
一.什么是线程安全性 编写线程安全的代码 核心在于要对状态訪问操作进行管理. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与 ...
- 多线程之:正确使用 Volatile 变量
转载:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile ...
- Java 理论与实践: 正确使用 Volatile 变量--转
原文地址:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 ...
- Java 理论与实践: 正确使用 Volatile 变量
Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并 ...
- 正确使用 Volatile 变量——Brian Goetz
本文转自:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html 由Java并发大师Brian Goetz所撰写的. Java 语言中的 v ...
- <转>Java 理论与实践: 正确使用 Volatile 变量
Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”:与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少, ...
随机推荐
- 4. SOFAJRaft源码分析— RheaKV初始化做了什么?
前言 由于RheaKV要讲起来篇幅比较长,所以这里分成几个章节来讲,这一章讲一讲RheaKV初始化做了什么? 我们先来给个例子,我们从例子来讲: public static void main(fin ...
- SpringBoot系列教程之Bean之指定初始化顺序的若干姿势
上一篇博文介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢? 本文将介绍几种可行的方式来控制 bean 之间 ...
- Java_条件控制与循环控制
条件控制语句: 1. if-else语句 if(条件1){ 代码块1; }else if(条件2){ 代码块2; }else{ 代码块3; } 2. switch语句 switch(变 ...
- C#基本网络操作
建档操作如ping,查询本机主机ip,同步异步查询局域网内主机,同步异步邮件发送等 1)ping 通过ping类测试网络 using System; using System.Text; using ...
- python编译器的安装和pycharm的安装
python编译器的安装 进入官网https://www.python.org/,根据提示安装 安装python编译器 pychram安装 下载地址: https://www.jetbrains.co ...
- Mac高效开发之iTerm2、Prezto和Solarized主题
本文首发于个人网站:Mac高效开发之iTerm2.Prezto和Solarized主题 工欲善其事必先利其器,作为开发,我追求极致的高效,因此会在很多细节上追求效率,例如:命令行窗口敲命令的时候,如果 ...
- 解决axios发送post请求,后端接收不到数据
https://segmentfault.com/a/1190000012635783
- Flask框架实现给视图函数增加装饰器操作示例
在@app.route的情况下增加装饰器的写法: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2 ...
- 无法打开msvcrtd.lib
- Mybaits 源码解析 (七)----- Select 语句的执行过程分析(下篇)(Mapper方法是如何调用到XML中的SQL的?)全网最详细,没有之一
我们上篇文章讲到了查询方法里面的doQuery方法,这里面就是调用JDBC的API了,其中的逻辑比较复杂,我们这边文章来讲,先看看我们上篇文章分析的地方 SimpleExecutor public & ...