概要
volatile关键字主要作用是使变量在多个线程之间可见。
在线程工作的时候,每个线程都会有自己的一个内存区域,这个区域保存了主内存中线程需要用到的一些变量和引用。当多个线程访问一个没有同步的变量时候,每个线程自己内存中的变量对于其它线程是不可见的。如图:
图中线程1与线程2的count变量是互不可见的。
但是,当一个变量如果被volatile修饰之后。那么多个线程之间对于这个变量则是互相可见的。因为当一个变量被volatile修饰之后,那么任何线程在load这个变量的时候,编译器会强制所有线程读取这个变量都从主内存中去读取。这样每个线程读取的都会是一样的数据。示例:
public class MyThread implements Runnable {
private boolean isRunning = true;
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("开始运行。。。。。");
while(isRunning) {
}
System.out.println("终止运行。。。。。");
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
t1.start();
t1.sleep(2000);
myThread.setRunning(false);
System.out.println("设置运行结束");
t1.sleep(1000);
System.out.println("当前isRunning = " + myThread.isRunning);
}
}
代码运行结果:
可以看到设置了isRunning为false。程序并没有退出。这说明t1线程读取的是自身的内存变量。不是主内存。
volatile具有可见性,但不具有原子性
volatile只能让多个线程之间具有可见性,并不能具有原子性。示例:
public class MyThread extends Thread {
private static volatile int count ;
public static void add() {
for(int i = 0 ; i < 1000; i++){
count++;
}
System.out.println(count);
}
@Override
public void run() {
add();
}
public static void main(String[] args) throws InterruptedException {
MyThread[] threads = new MyThread[10];
for(int j = 0; j < 10; j++) {
threads[j] = new MyThread();
}
for(int i = 0; i < 10; i++) {
threads[i].start();
}
}
}
运行结果:
2000
4236
5305
5638
6638
3000
8152
2000
9059
8433
运行结果并没有是10000.这说明volatile关键字修饰变量并不具有原子性。其原因是因为对于对一个变量进行操作时。内存的操作步骤是:
read and load 从主存复制变量到当前工作内存
use and assign 执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容
但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样。具体详细分析,以后在学习JVM中会进行具体分析。