一、Java并发中的3个概念:
1.原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。举个最简单的例子,大家想一下假如为一个32位的变量赋值过程不具备原子性的话,会发生什么后果?
x = 10; //语句1
y = x; //语句2
x++; //语句3
x = x + 1; //语句4
语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。
所以上面4个语句只有语句1的操作具备原子性。
2.可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。“可见性”并非是说修改了之后,会”通知”其他线程去取最新的数据,而是下次有线程来取这个数据的时候,禁止他们从自己的线程缓存中取数据,直接到原始的内存去取最新的值。
例子:
//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;
假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。
3.有序性:即程序执行的顺序按照代码的先后顺序执行。
4.要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
二、volatile关键字
1.执行结果非常可能不是1000000,例如在我的电脑上执行结果为40多万。
public class Test2 {
public volatile int count = 0;
public void increase() {
count++;
}
public static void main(String[] args) {
final Test2 test = new Test2();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<100000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.count);
}
}
2.原因:volatile 只能保证 “可见性”,不能保证 “原子性”。
count++; 这条语句由3条指令组成:
(1)将 count 的值从内存加载到 cpu 的某个寄存器r
(2)将 寄存器r 的值 +1,结果存放在 寄存器s
(3)将 寄存器s 中的值写回内存在没有 volatile 的时候,执行完 count++;,执行结果其实是写到CPU缓存中,没有马上写回到内存中,后续在某些情况下(比如CPU缓存不够用)再将CPU缓存中的值flush到内存。正因为没有马上写到内存,所以不能保证其它线程可以及时见到执行的结果。
- 在有 volatile 的时候,执行完 count++;,执行结果写到CPU缓存中,并且同时写回到内存,因为已经写回内存了,所以可以保证其它线程马上看到执行的结果。
- 可能有两个线程同时执行(1),所以(2)计算出来一样的结果,然后(3)存回的也是同一个值。
- 如果线程1已经读取了count的值,还没有进行++操作,这个时候线程2读取了内存中的count值,这时候线程1执行count++,接着线程2再执行count++。 在线程1执行完count++之后,线程2执行count++之前,线程2还会去内存中读取count的值吗?不会,所以这就导致了异步的情况。
来源:
java中关于volatile的理解疑问 回答1
java中关于volatile的理解疑问 回答2
Java并发编程:volatile关键字解析