文/属衣
Java内存模型的抽象结构示意图
Java内存模型包含主内存和工作内存(本地内存),所有的变量都存储在主内存中。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
A线程与B线程的通信过程:1)线程A把本地内存共享变量刷新到主内存 2)B线程读取A线程已更新过的共享变量
但实际情况,A线程与B线程的通信会出现“脏读”等问题。
1.原子性
原子性即一个操作或者多个操作要么全部成功执行,要么都不执行。
int i=10;① i--;② j=i; ③
只有语句①是原子性,其余需要先读取变量再进行赋值,所以是非原子性。
2.可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
int i=10;① j=i;②
线程A在执行语句①,并未将赋值后的结果刷新到主内存,线程B进行语句②操作时,导致读取到旧值,无法保证可见性。
3.有序性
有序性即程序执行的顺序按照代码的先后顺序执行。一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
线程A:
context = loadContext();
①
inited =
true
;
②
线程B:
while
(!inited ){
sleep()
}
initConfig(context);
①②
可能会被重排序,假如线程A先执行了语句②
,会导致线程B直接跳过while循环,导致
initConfig(context)
失败!
volatile
volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。
1.使用volatile修饰的共享变量(类的成员变量、类的静态成员变量),保证了可见性与有序性(禁止进行指令重排序)。
线程A:
boolean
stop =
false
;
while
(!stop){
doSomething();
}
线程B:
stop =
true
;
public
class
Test {
public
volatile
int
inc =
0
;
public
void
increase() {
inc++;
}
public
static
void
main(String[] args) {
final
Test test =
new
Test();
for
(
int
i=
0
;i<
10
;i++){
new
Thread(){
public
void
run() {
for
(
int
j=
0
;j<
1000
;j++)
test.increase();
};
}.start();
}
while
(Thread.activeCount()>
1
)
//保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
increase
不是原子性的,假如线程1执行时inc=10,线程1先将inc读取到工作内存(此时保证读取到的是最新结果),此时被阻塞。线程2将inc读取到工作内存并进行+1操作,然后放入主内存inc=11,线程1接着进行inc+1操作(inc已经被读取过了inc=10),所以会将inc=11放入主内存。
volatile的实现原理
1.可见性
处理器为了提高处理速度,不直接和内存进行通讯,而是将系统内存的数据独到内部缓存后再进行操作,但操作完后不知什么时候会写到内存。
如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。 这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。
但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查 自己的缓存是否过期,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。 这一步确保了其他线程获得的声明了volatile变量都是从主内存中获取最新的。
2.有序性
Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
volatile的应用场景
1.状态标记
2.单例double check
class
Singleton{
private
volatile
static
Singleton instance =
null
;
private
Singleton() {
}
public
static
Singleton getInstance() {
if
(instance==
null
) {
synchronized
(Singleton.
class
) {
if
(instance==
null
)
instance =
new
Singleton();
}
}
return
instance;
}
}