volatile

本文深入探讨Java内存模型及volatile关键字的特性与作用,包括可见性、非原子性和有序性,通过实例解释如何避免活性失败和安全性失败,并强调volatile在多线程环境下防止指令重排序的重要性。

推荐一篇厉害的文章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 }
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值