这里我们先来分析一段代码:
class MyData{
volatile int number = 0;
//请注意,此时number前面时加了volatile关键字修饰的,volatile不保证原子性
public void addPlusPlus(){
number ++;
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果看是多少
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t int type,finally number value:" + myData.number);
}
}
如果volatile保证原子性,那么上段代码输出的结果就一定是20000,如果不保证原子性那么上面的代码可能输出小于20000的值。我们看下控制台输出:
这输出值小于20000,则说明volatile不保证原子性。
解决方法:
1.使用synchronized关键字进行加锁(但是这个锁有点重了,在此不做讨论)
2.使用juc下的AtomicInteger
我们来看一下为什么会输出小于20000的值?
我们这里定义了一个全局变量number ,但是呢,我们看一下number++的字节码
0 aload_0
1 dup
2 getfield #2 <VolatileDemo/MyData.number>
5 iconst_1
6 iadd
7 putfield #2 <VolatileDemo/MyData.number>
10 return
在这里我们可以看到number++的执行共有四条指令(getfield->putfield),假设我们有两个线程分别是thread1和thread2,线程thread1执行完iadd后突然被挂起,这时thread2获得执行权之后thread2操作完变量number后将值写入主内存(参见volatile保证可见性)中,但是这时thread1也会将变量number写入到主内存中,此时就出现了并发问题。
解决后代码:
class MyData{
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic(){
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果看是多少
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type,finally number value:" + myData.atomicInteger);
}
}
此时查看打印结果: