从volatile说到i++的线程安全问题

一般说来,volatile用在如下的几个地方: 1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2 中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
volatile关键字保证了
1. 可见性——在多线程环境下,被修饰的变量在别修改后会马上同步到主存,这样该线程对这个变量的修改就是对所有其他线程可见的,其他线程能够马上读到这个修改后值。
2. 禁止指令重排序优化
本文中来谈谈第一点,可见性。
thread的本地内存
每个thread都拥有自己的线程存储空间
thread何时同步本地存储空间的数据到主存是不确定的
例子
上图表示两个线程并发执行,而且代码顺序上为thread1-》thread2
不用 volatile
假如ready字段不使用volatile,那么thread 1对ready做出的修改对于thread2来说未必是可见的,是否可见是不确定的。假如此时thread1 ready泄露了(leak through)了,那么thread 2可以看见ready为true,但是有可能answer的改变并没有泄露,则thread2有可能会输出 0 (answer=42对thread2并不可见)
使用 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--”);
}
}
}12345678910111213141516171819202122232425262728293031323334353637383940414243
最后输出的结果是
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.37s123456
原因是i++和++i并非原子操作,我们若查看字节码,会发现
void f1() { i++; }1
的字节码如下
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: return123456789
可见i++执行了多部操作, 从变量i中读取读取i的值 -》 值+1 -》 将+1后的值写回i中,这样在多线程的时候执行情况就类似如下了
thread1 thread2
r1 = i; r3 = i;
r2 = r1 + 1; r4 = r3 + 1;
i = r2; i = r4;1234
这样会造成的问题就是 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--”);
}
}
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
输出
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

华米Amazfit GTR 2、GTS 2固件发布:运动模式增加到90项
纳思达:上半年营收123亿元,极海微芯片出货2.38亿颗
VR最伟大?五感相通 这是一场人机交互的盛宴
2018 Web Summit最受人关注的AI论点
基于5G工业路由器的建筑塔吊无线监测方案
从volatile说到i++的线程安全问题
红外灭菌器新品上架
RFID智能样品管理是如何实现的
北斗导航产业规模完善 芯片抢滩智能手机
福特将电动汽车领域的增加投资至220亿美元
区块链生态系统Friend可解决现有数据存储系统的安全挑战
家乐福正式宣布与顺丰合作的无人机诞生
减速马达调速器使用注意事项
美光后段封测厂启动 封测厂进入新战国时代
Intel与ARM双星拱月 Imagination MIPS强攻微服务器
广明源家用定时遥控紫外线消毒灯:更智能、更安全
芯片板块股票有哪些 芯片板块股票一览
基于Proteus与单片机的交通灯控制电路设计
基于BS45F,BS83B,HT67的无线遥控浴霸方案
WiFi~5G,国产射频前端芯片厂商迎来新机遇!