学习volatile的时候也许我们会看到下面这句话:
对于volatile变量来说,自增操作线程不安全。 |
那为什么不安全呢?本帅博主看的是《并发编程的艺术》这本书,这本书对这一事件也没有做出很让人易懂的解释。那么我们自己用例子来测试一下咯~
测试代码如下:
package xiancheng;
public class volatileTest {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
VolatileExample example=new VolatileExample();
Thread thread1=new Thread() {
public void run(){
for(int i=0;i<100000;i++) {
example.getAndIncrement();
}
}
};
Thread thread2=new Thread() {
public void run(){
for(int i=0;i<100000;i++) {
example.getAndIncrement();
}
}
};
thread1.start();
thread2.start();
Thread.sleep(6000);//等一会,让线程都执行完。
System.out.println(example.get());
}
}
class VolatileExample{
volatile int v=0;
public void set(int l) {
v=l;
}
public void getAndIncrement() {
v++;
}
public int get() {
return v;
}
}
在上面这个代码中,VolatileExample类汇总声明了一个volatile变量v,以及三个方法,set()、get()和用来进行自增操作的getAndIncrement()方法。
在主函数中,我创建了两个线程,各自循环调用VolatileExample类的实例volatileExample中的getAndIncrement()方法100000次。
如果volatile保证++安全,那么执行结果将是200000.
那么真实结果是多少呢?
多执行几次:
从上面几次的执行结果我们可以看到,始终都不到200000万。这样的结果表明volatile不保证volatile++这样的操作具有原子性。
为什么呢?
其实很简单,因为“++”属于复合操作。
上面的VolatileExample其实等价于一下的代码:
class VolatileExample{
int v=0;
public synchronized void set(int l) {
v=l;
}
public void getAndIncrement() {
int temp=get();
temp+=1;
set(temp);
}
public synchronized int get() {
return v;
}
}
如果不清晰的话,我们可以通过javap反编译一下。
使用javap对VolatileExample类进行反编译,出来的结果如下:
我们可以看到,getAndIncrement()方法中的v++语句被编译成了 七条语句,这属于复合操作。
v++其实相当于:
- 读v
- 对v+1;
- 将原来的v值置为v+1。
volatile保证可见性,当进行++操作的时候,volatile保证第一条指令正确,即读正确。当执行接下来的指令的时候,其他线程可能对v加大了,当将v存回去的时候(即执行putfield指令的时候),可能将一个更小的v同步回主内存去了。所以最终得到的数字就会小于200000.
总结:volatile的读写具有原子性,但是自增操作属于复合操作,因此不具有原子性,所以线程也不安全。
好啦,以上就是关于volatile变量自增是否线程安全的相关知识总结,如果大家有什么不明白的地方或者发现文中有描述不好的地方,欢迎大家留言评论,我们一起学习呀。
Biu~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~pia!