Java内存模型的抽象
在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元
素)。局部变量、方法定义参数、异常处理器参数不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
所谓线程安全无非是要控制多个线程对某个资源的有序访问和修改。总结Java的内存模型,要解决两个主要问题:可见性和有序性
1. 可见性问题
多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型规定Jvm有主内存,主内存是多个线程共享的。当new一个对象的时候是分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主内存中对象的副本
![](http://cdn2.infoqstatic.com/statics_s2_20160414-0116/resource/articles/java-memory-model-1/zh/resources/11.png)
线程操作某个对象时,执行顺序如下:
1. 从主内存复制变量到当前工作内存(read and load)
2. 执行代码,改变共享变量值(use and assign)
3. 将工作内存数据刷新到主内存(store and write)
Jvm规范定义了线程对主内存的操作指令:read 、load 、 use 、 assign 、 store 、 write。当一个共享变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程如果没有看到这个被修改后的值,这就是多线程的可见性问题。
2. 有序性问题
假设共享变量x现在值为10,线程a加1,线程b减1,从表面上看,似乎最终x还是10,但是多线程情况下会有这种情况发生:
1:线程a从主内存读取x副本到工作内存,工作内存中x值为10
2:线程b从主内存读取x副本到工作内存,工作内存中x值为10
3:线程a将工作内存中x加1,工作内存中x值为11
4:线程a将x提交主内存中,主内存中x为11
5:线程b将工作内存中x值减1,工作内存中x值为9
6:线程b将x提交到中主存中,主存中x为9
Jvm在真正执行代码的时候并不一定是完全按照代码的真正顺序执行,这就是所谓的指令重排序
什么是指令重排序?一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序和代码中的顺序一致。
如:
int a = 10; // 语句1
int r = 2; // 语句2
a = a + 3; // 语句3
r = a * a; // 语句4
这段代码有4个语句,那么可能的一个执行顺序是:
语句2 -> 语句1 -> 语句3 -> 语句4
指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
synchronized关键字
Java使用synchronized关键字作为多线程并发环境下的执行有序性的保证手段之一,当一段代码会修改共享变量,加了synchronized后这段代码就变成了互斥区
synchronized(锁){
// 互斥区
}
理论上每个对象都可以作为锁,但一个对象作为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁时没有意义的。下面代码中,lock变量作为锁根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock = new Object(); 每个线程都有自己的lock,根本不存在锁竞争。
- public class ThreadTest{
- public void test(){
- Object lock=new Object();
- synchronized (lock){
- //do something
- }
- }
- }
1 获得同步锁
2 清空工作内存
3 从主存拷贝变量副本到工作内存
4 对这些变量计算
5 将变量从工作内存写回到主存
6 释放锁
volalile关键字
volatile是java提供的一种同步手段,只不过它是轻量级的同步
1. 保证变量在各个线程的可见性,意思就是这个变量的值一修改,其他线程可以立刻得知。而一个普通变量需要先写回主内存,然后其他线程去读取这个值。
2. 禁止指令重排序优化,但不是完全严格的顺序
1
2
3
4
5
6
7
8
|
//x、y为非volatile变量
//flag为volatile变量
x =
2
;
//语句1
y =
0
;
//语句2
flag =
true
;
//语句3
x =
4
;
//语句4
y = -
1
;
//语句5
|
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
使用场景:状态标记量, 如果v不是volatile变量,那么编译器可以在writer线程中重排序写入操作,那么reader线程中的读取x变量的操作可能会看到0class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
http://www.infoq.com/cn/articles/java-memory-model-1
http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
http://hllvm.group.iteye.com/group/wiki/2877-synchronized-volatile
http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html