volatile的两个作用
阻止指令重排序
内存可见性(即每次变量的读取都从主存中读取,不从cpu高速缓存读取)
volatile变量自增与线程安全性的测试
static volatile int count = 0;
public static void main(String[] args) {
final long next = System.currentTimeMillis()+2000;
Runnable runnable = new Runnable() {
@Override
public void run() {
while (System.currentTimeMillis()<next);//自旋,尽量使线程同一时间执行count++
count++;
}
};
for (int i = 0; i < 40; i++) {
new Thread(runnable).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//测试最终输出结果可能为38,39,40等
System.out.println(count);
}
结论:volatile关键字无法保证volatile变量操作(非单独读写操作)的原子性,即volatile变量的操作不是线程安全的
以count++为例,这里可以分成3步原子性操作
1.从主存读取count值
2.执行+1操作(A线程执行此操作,同时B线程执行读取操作,此时A线程还没执行操作3,而B线程开始执行操作2,结果造成count值多加一次)
3.将+1后的值写回主存
volatile内存可见性测试
static int i = 0;
static int count = 0;
public static void main(String[] args) {
//main线程sleep的时间,从3开始线程听不下来,该值小于2的时候线程会停止
//如果静态变量count和i只要有一个声明为volatile变量,无论该值多大,线程都会停止
//静态变量不加volatile,该线程停止临界值值会随着循环方法里代码执行效率而改变
long waitTime = 3;
Runnable runnable = new Runnable() {
@Override
public void run() {
while (i<=0){
count++;
}
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnable).start();
}
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
i = 1;
System.out.println(count);
}
为什么不加volatile关键字,main线程设置不同的休眠时间,得到的结果会不同呢?
初步理解:
不加volatile的话,run方法体里的循环代码在运行期有可能成为热点代码被JIT编译器编译成机器码,因此时间临界值内循环体内的代码还未成为热点代码,可以从主存读取main方法修改i后的值,成为热点代码被编译成机器码后就不在从主存读取i的值了
加volatile的话,run方法体的循环代码在运行期也会被JIT编译器优化,但优化后的机器码依然会从主存中读取i的值,因此循环体可以停止