volatile关键字:
- 保证线程间的可见性
- 禁止指令的重排序
- 不能保证原子性,所以不是线程安全的
线程间可见性
import java.util.concurrent.TimeUnit;
public class test extends Thread {
volatile int x = 0;//此处可以将volatile去除 或者 替换为 static,经过对比可看出volatile的作用
private void write() {
x = 5;
}
private void read() {
while (x != 5) {
}
if(x == 5){
System.out.println("------stoped");
}
}
public static void main(String[] args) throws Exception {
test example = new test();
Thread writeThread = new Thread(new Runnable() {
public void run() {
example.write();
}
});
Thread readThread = new Thread(new Runnable() {
public void run() {
example.read();
}
});
readThread.start();
TimeUnit.SECONDS.sleep(5);//记住此处一定要暂停5秒,以保证writeThread一定会在readThread中执行
System.out.println("------");
writeThread.start();
}
}
如果不加volatile关键字,readThread 在while循环中高频率的读取x的值,此时读取是readThread 的工作内存中的值,一直是0。虽然writeThread将x赋值为5,但readThread 一直读取的不是最新值,导致程序一直运行不退出。
加了volatile关键字,writeThread修改的值会立刻写入主存,readThread 读取的也是主内存最新的值,所以会打印"------stoped",并结束程序。
禁止指令重排序
为什么会有指令重排序:
- 在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。
- 在硬件层面,CPU会将接收到的一批指令按照其规则重排序,同样是基于CPU速度比缓存速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序
使用volatile关键字会禁止指令的重排序
不能保证原子性,所以不是线程安全的
public class test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final test test = new test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
加volatile关键字和不加发现最后输出的inc的值都不一定是10000;所以volatile不是线程安全的。
保证线程安全使用synchronized, lock。加上锁之后可保证每次输出的结果都是10000,不放代码了,可自行在increase方法上尝试。
注:可能有人不理解,volatile在并发编程中并不常用,但为什么还要学习它呢?
我来说明一下,因为它对理解Java内存模型很有用,用懂了这个关键字是对JMM很好的理解过程。
实际上对于线程间的可见性,不加此关键字的变量在线程A中操作后,早晚也是要同步到主线程的,volatile只是加快了这个时间。在现代版本的JVM中,优化了运行效率,所以立即同步特点不会特别明显,不过为了更好的学习和理解Java运行过程,我们仍然需要清楚这个关键字的含义呀。