推荐一篇厉害的文章http://ifeve.com/java-memory-model-1/
讲的很底层 适合在看完本文后看
本文借鉴自http://www.cnblogs.com/hapjin/p/5492880.html
1.volatile的可见性
内存模型
(每个线程都有自己的私有内存,都共享着主内存。)
每个线程的操作:
1.从主内存读取变量,成为一份拷贝变量,放在私有内存中(私有内存是抽象的概念,实际不存在,它包括缓存、编译器优化等)
2.对拷贝变量进行操作
3.将修改后的拷贝变量刷新回主内存
一个例子
1 public class RunThread extends Thread { 2 3 private boolean isRunning = true; 4 5 public boolean isRunning() { 6 return isRunning; 7 } 8 9 public void setRunning(boolean isRunning) { 10 this.isRunning = isRunning; 11 } 12 13 @Override 14 public void run() { 15 System.out.println("进入到run方法中了"); 16 while (isRunning == true) { 17 } 18 System.out.println("线程执行完成了"); 19 } 20 } 21 22 public class Run { 23 public static void main(String[] args) { 24 try { 25 RunThread thread = new RunThread(); 26 thread.start(); 27 Thread.sleep(1000); 28 thread.setRunning(false); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 } 33 }
结果是setRunning无效,线程中的循环变成死循环。称之为“活性失败”。
原因:其实涉及到两个线程的问题。一个是主线程,一个是子线程。主线程进行了isRunning从主内存中的读取,成为一份拷贝变量,刷新回了主内存。(在JVM处于-server模式下运行)而子线程不会进行主内存的读取,一直读取拷贝变量。
解决:把isRunning用volatile标识,强制让子线程读取主内存。
(所以引申出了volatile的作用,强制子线程读取子内存)
2.volatile的非原子性
比如100个子线程,分别对用volatile标识的i自增100次,i的值会到达10000吗?
如第一个性质,volatile标识的变量,会强制从主内存中读取,但是各个线程却来不及将修改后的值都刷新回去。
称之为安全性失败
3.此外,volatile关键字修饰的变量不会被指令重排序优化
线程A执行的操作如下:
Map configOptions ;
char[] configText;
volatile boolean initialized = false;
//线程A首先从文件中读取配置信息,调用process...处理配置信息,处理完成了将initialized 设置为true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//负责将配置信息configOptions 成功初始化
initialized = true;
线程B等待线程A把配置信息初始化成功后,使用配置信息去干活.....线程B执行的操作如下:
while(!initialized)
{
sleep();
}
//使用配置信息干活
doSomethingWithConfig();
如果initialized变量不用 volatile 修饰,在线程A执行的代码中就有可能指令重排序。
即:线程A执行的代码中的最后一行:initialized = true 重排序到了 processConfig方法调用的前面执行了,这就意味着:配置信息还未成功初始化,但是initialized变量已经被设置成true了。那么就导致 线程B的while循环“提前”跳出,拿着一个还未成功初始化的配置信息去干活(doSomethingWithConfig方法)。。。。
因此,initialized 变量就必须得用 volatile修饰。这样,就不会发生指令重排序,也即:只有当配置信息被线程A成功初始化之后,initialized 变量才会初始化为true。综上,volatile 修饰的变量会禁止指令重排序(有序性)
(重排序在推荐的文章中)1 public class RunThread extends Thread { 2 3 private boolean isRunning = true; 4 5 public boolean isRunning() { 6 return isRunning; 7 } 8 9 public void setRunning(boolean isRunning) { 10 this.isRunning = isRunning; 11 } 12 13 @Override 14 public void run() { 15 System.out.println("进入到run方法中了"); 16 while (isRunning == true) { 17 } 18 System.out.println("线程执行完成了"); 19 } 20 } 21 22 public class Run { 23 public static void main(String[] args) { 24 try { 25 RunThread thread = new RunThread(); 26 thread.start(); 27 Thread.sleep(1000); 28 thread.setRunning(false); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 } 33 }