您的位置:首页 > 编程语言 > Java开发

Java volatile

2016-03-26 14:59 369 查看

Java多线程工作内存

对于Java多线程程序,每个线程有自己的线程工作内存和主内存。

其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图

描述这写交互



主内存主要对应于java堆中对象的实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。

从更底层来说,主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存。

volatile解决的问题

线程每次读取的是主内存的值,每次读取会把主内存的值同步到线程工作内存。

每次写入会把工作内存的值同步到主内存。

volatile的问题

假如:

A = 1;线程一二相隔很近读取了A,A都等于1

线程1:

A = A + 1 ;

线程2:

A = A + 2;

则运行完会有一个线程的修改不生效。

经典的例子:

package mythread;

public class JoinThread extends Thread {
public static volatile int n = 0;
public void run() {
for (int i = 0; i < 10; i++)
try {
n = n + 1;
sleep(3); //  为了使运行结果更随机,延迟3毫秒

} catch (Exception e) {}
}

public static void main(String[] args) throws Exception {

Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
//  建立100个线程
threads[i] = new JoinThread();
for (int i = 0; i < threads.length; i++)
//  运行刚才建立的100个线程
threads[i].start();
for (int i = 0; i < threads.length; i++)
//  100个线程都执行完后继续
threads[i].join();
System.out.println(" n= " + JoinThread.n);
}
}


如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面代码时,很多时侯输出的n都小于1000,这说明n=n+1,n++都不是原子级别的操作。

自增运算其实是由4条字节码组成的:

getstatic
iconst_1
iadd
putstatic


当getstatic指令把race的值取到操作数栈顶的时候,volatile关键字保证race的值在此时是正确的。

但是在执行iconst_1和iadd的时候,其他线程可能已经把race的值加大了,而在操作数栈顶的值就变成了过期数据。

所以putstatic就会写回较小数据。

正确使用 volatile 变量的条件

只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

对变量的写操作不依赖于当前值,或者能够确保只有单一的线程修改变量的值

比如线程1:A = 2,线程2:A = 3。

那么A就算设置volatile也没有用

该变量没有包含在具有其他变量的不变式中,该变量不需要与其他状态变量共同参与不变约束

禁止指令重排序优化

普通变量仅仅保证在该方法执行过程中所有依赖赋值结果的地方能获取到正确的值,而不能保证变量赋值操作的顺序和程序代码的执行顺序一致。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: