从volatile说到i++的线程安全问题
简介
volatile关键字保证了在多线程环境下,被修饰的变量在别修改后会马上同步到主存,这样该线程对这个变量的修改就是对所有其他线程可见的,其他线程能够马上读到这个修改后值.
Thread的本地内存
- 每个Thread都拥有自己的线程存储空间
- Thread何时同步本地存储空间的数据到主存是不确定的
例子

借用Google JEREMY MANSON 的解释,上图表示两个线程并发执行,而且代码顺序上为Thread1->Thread2
1. 不用 volatile
假如ready字段不使用volatile,那么Thread 1对ready做出的修改对于Thread2来说未必是可见的,是否可见是不确定的.假如此时thread1 ready泄露了(leak through)了,那么Thread 2可以看见ready为true,但是有可能answer的改变并没有泄露,则thread2有可能会输出 0 (answer=42对thread2并不可见)
2. 使用 volatile
使用volatile以后,做了如下事情
- 每次修改volatile变量都会同步到主存中
- 每次读取volatile变量的值都强制从主存读取最新的值(强制JVM不可优化volatile变量,如JVM优化后变量读取会使用cpu缓存而不从主存中读取)
- 线程 A 中写入 volatile 变量之前可见的变量, 在线程 B 中读取该 volatile 变量以后, 线程 B 对其他在 A 中的可见变量也可见. 换句话说, 写 volatile 类似于退出同步块, 而读取 volatile 类似于进入同步块
所以如果使用了volatile,那么Thread2读取到的值为read=>true,answer=>42,当然使用volatile的同时也会增加性能开销
注意
volatile并不能保证非源自性操作的多线程安全问题得到解决,volatile解决的是多线程间共享变量的可见性问题,而例如多线程的i++,++i,依然还是会存在多线程问题,它是无法解决了.如下:使用一个线程i++,另一个i--,最终得到的结果不为0
public class VolatileTest {
private static volatile int count = 0;
private static final int times = Integer.MAX_VALUE;
public static void main(String[] args) {
long curTime = System.nanoTime();
Thread decThread = new DecThread();
decThread.start();
// 使用run()来运行结果为0,原因是单线程执行不会有线程安全问题
// new DecThread().run();
System.out.println("Start thread: " + Thread.currentThread() + " i++");
for (int i = 0; i < times; i++) {
count++;
}
System.out.println("End thread: " + Thread.currentThread() + " i--");
// 等待decThread结束
while (decThread.isAlive());
long duration = System.nanoTime() - curTime;
System.out.println("Result: " + count);
System.out.format("Duration: %.2fs\n", duration / 1.0e9);
}
private static class DecThread extends Thread {
@Override
public void run() {
System.out.println("Start thread: " + Thread.currentThread() + " i--");
for (int i = 0; i < times; i++) {
count--;
}
System.out.println("End thread: " + Thread.currentThread() + " i--");
}
}
}
最后输出的结果是
Start thread: Thread[main,5,main] i++
Start thread: Thread[Thread-0,5,main] i--
End thread: Thread[main,5,main] i--
End thread: Thread[Thread-0,5,main] i--
Result: -460370604
Duration: 67.37s
原因是i++和++i并非原子操作,我们若查看字节码,会发现
void f1() { i++; }
的字节码如下
void f1();
Code:
0: aload_0
1: dup
2: getfield #2; //Field i:I
5: iconst_1
6: iadd
7: putfield #2; //Field i:I
10: return
可见i++执行了多部操作, 从变量i中读取读取i的值 -> 值+1 -> 将+1后的值写回i中,这样在多线程的时候执行情况就类似如下了
Thread1 Thread2
r1 = i; r3 = i;
r2 = r1 + 1; r4 = r3 + 1;
i = r2; i = r4;
这样会造成的问题就是 r1, r3读到的值都是 0, 最后两个线程都将 1 写入 i, 最后 i 等于 1, 但是却进行了两次自增操作
可知加了volatile和没加volatile都无法解决非原子操作的线程同步问题
线程同步问题的解决
Java提供了java.util.concurrent.atomic 包来提供线程安全的基本类型包装类,例子如下
package com.qunar.atomicinteger; import java.util.concurrent.atomic.AtomicInteger; /**
* @author zhenwei.liu created on 2013 13-9-2 下午10:18
* @version $Id$
*/
public class SafeTest { private static AtomicInteger count = new AtomicInteger(0);
private static final int times = Integer.MAX_VALUE; public static void main(String[] args) { long curTime = System.nanoTime(); Thread decThread = new DecThread();
decThread.start(); // 使用run()来运行结果为0,原因是单线程执行不会有线程安全问题
// new DecThread().run(); System.out.println("Start thread: " + Thread.currentThread() + " i++"); for (int i = 0; i < times; i++) {
count.incrementAndGet();
} // 等待decThread结束
while (decThread.isAlive()); long duration = System.nanoTime() - curTime;
System.out.println("Result: " + count);
System.out.format("Duration: %.2f\n", duration / 1.0e9);
} private static class DecThread extends Thread { @Override
public void run() {
System.out.println("Start thread: " + Thread.currentThread() + " i--");
for (int i = 0; i < times; i++) {
count.decrementAndGet();
}
System.out.println("End thread: " + Thread.currentThread() + " i--");
}
}
}
输出
Start thread: Thread[main,5,main] i++
Start thread: Thread[Thread-0,5,main] i--
End thread: Thread[Thread-0,5,main] i--
Result: 0
Duration: 105.15
结论
- volatile解决了线程间共享变量的可见性问题
- 使用volatile会增加性能开销
- volatile并不能解决线程同步问题
- 解决i++或者++i这样的线程同步问题需要使用synchronized或者AtomicXX系列的包装类,同时也会增加性能开销
从volatile说到i++的线程安全问题的更多相关文章
- Java并发编程基础-线程安全问题及JMM(volatile)
什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...
- stl空间配置器线程安全问题补充
摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关 ...
- Servlet线程安全问题
Servlet采用单实例多线程方式运行,因此是线程不安全的.默认情况下,非分布式系统,Servlet容器只会维护一个Servlet的实例,当多个请求到达同一个Servlet时,Servlet容器会启动 ...
- DP #1 Singleton Pattern线程安全问题
单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例. 其中涉及到最主要的问题就是在多线程并发时线程安全问题. 单例模式的实现也有一个循序渐进的过程:1.最基本要求:每次从getI ...
- Java 中线程安全问题
不好意思,一个国庆假期给我放的都不知道东西南北了,放松,很放松,差一点就弃更了,感谢那些催更的小伙伴们! 虽然没有更新,但是日常的学习还是有的,以后我尽量给大家分享一些通用知识,非技术. 但是本期还是 ...
- java线程安全问题原因及解决办法
1.为什么会出现线程安全问题 计算机系统资源分配的单位为进程,同一个进程中允许多个线程并发执行,并且多个线程会共享进程范围内的资源:例如内存地址.当多个线程并发访问同一个内存地址并且内存地址保存的值是 ...
- 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题
再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁. 什么是内置锁? 在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对 ...
- java并发学习--第三章 线程安全问题
线程的安全问题一直是我们在开发过程中重要关注的地方,出现线程安全问题的必须满足两个条件:存在着两个或者两个以上的线程:多个线程共享着共同的一个资源, 而且操作资源的代码有多句.接下来我们来根据JDK自 ...
- Java 线程安全问题的本质
原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: 线程安全问题的本质 理解CPU JVM虚拟机类比于操作系统 重排序 汇总 一些解释 ...
随机推荐
- jQuery EasyUI-DataGrid动态加载表头
项目总结—jQuery EasyUI-DataGrid动态加载表头 目录(?)[-] 概要 实现 总结 概要 在前面两篇文章中,我们已经介绍了在jQuery EasyUI-DataGrid ...
- JXOI2017-2018 解题报告
链接:JXOI2017-2018 解题报告 代码预览:Github
- Java内存是怎么管理的
JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 ( ...
- Javascript中call方法和apply方法用法和区别
第一次在博客园上面写博客,知识因为看书的时候发现了一些有意思的知识,顺便查了一下资料,就发到博客上来了,希望对大家有点帮助. 连续几天阅读<javascript高级程序设计>这本书了,逐渐 ...
- HTML 5 <form> enctype 属性
值 描述 application/x-www-form-urlencoded 在发送前对所有字符进行编码(默认). multipart/form-data 不对字符编码.当使用有文件上传控件的表单时, ...
- 【BZOJ 2688】 2688: Green Hackenbush (概率DP+博弈-树上删边)
2688: Green Hackenbush Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 42 Solved: 16 Description ...
- Struts2的概念
Struts2的概念 Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数 ...
- LuoguP5017 摆渡车 $dp$
题意 戳这里 吐槽 听同学说今年\(pjT3\)很难,于是就去看了下. 一眼斜率优化...为什么\(n,m\)这么小啊... 感觉这题出的还是不错的. Solution 首先我们先转化一波题意:给出数 ...
- WinlogonHack获取系统密码
实验环境: win03 sp1 Gina.dll与Msgina.dll Gina.dll在NT/2000中交互式的登陆支持是由WinLogon调用Gina.dll实现的,Gina.dll提供了一个交互 ...
- Apache之.htaccess备忘录(二)
博主热衷各种互联网技术,常啰嗦,时常伴有强迫症,常更新,觉得文章对你有帮助的可以关注我. 转载请注明"深蓝的镰刀" 书接上回,<Apache之.htaccess备忘录(一)& ...