2.Volatile不保证原子性

JMM之原子性

不可分割,完整性。也就是说某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要具体完成,要么同时成功,要么同时失败。

代码测试volatile是否保证原子性

我们创建了20个线程,然后每个线程分别循环1000次,来调用number++的方法

class MyData {
    // 定义int变量
    volatile int number = 0;

    public void addPlusPlus() {
        number++;
    }
}
public class Test {
    public static void main(String[] args) {
        MyData myData = new MyData();

        // 创建20个线程,线程里面进行1000次循环
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {

                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                }

            }).start();
        }

    /* 
        需要等待上面20个线程都执行完毕后,再用main线程取得最终的结果
        这里判断线程数是否大于2,为什么是2?因为默认有两个线程的,一个main线程,一个gc线程
    */
        while (Thread.activeCount() > 2) {
            Thread.yield(); // yield表示不执行
        }

        System.out.println("线程运行完后,number的值为:" + myData.number);
    }
}

线程执行完毕后打印number的值,假设volatile保证原子性的话,那么最后输出的值应该是20 * 1000 = 20000。

最终结果我们会发现,number输出的值并没有20000,而且是每次运行的结果都不一致的,这说明了volatile修饰的变量不保证原子性。

为什么出现数据丢失

A线程和B线程同时修改各自工作空间里的内容。因为可见性,需要重新写入内存,但是A线程在写入的时候,BB线程也同时写入,导致A线程的写入操作被挂起,这样造成B线程的写入后,A线程覆盖了B线程的值,造成了数据丢失的问题。

我们将一个简单的n++操作,针对 add() 这个方法的字节码文件进行分析:

volatile int n = 0;
public void add() {
    n++;
}
  public void add();
    Code:
       0: aload_0    
       1: dup
       2: getfield      
       5: iconst_1
       6: iadd
       7: putfield      
      10: return

我们能发现n++这条命令,被拆分为3个指令

  1. 执行getfield 从主内存拿到原始n
  2. 执行iadd 进行加1操作
  3. 执行putfileld 把累加后的值写回主内存

假如我们没有加synchronized那么第一步就可能存在着,三个线程同时通过getfield命令,拿到内存中的n值。然后三个线程,各自在自己的工作内存中进行加1操作,但是他们并发执行iadd命令的时候,因为只能一个进行写,所以其他操作会被挂起。假设A线程,先进行了写操作,在写完后,volatile的可见性应该告诉其他两个线程,主内存的值被修改了。但是因为太快,其他两个线程,陆续执行iadd命令,这就造成了其他线程没有接收到主内存n的改变,从而覆盖了原来的值,出现写丢失,这样也就让最终的结果少于20000。

如何解决

因此说明,在多线程环境下n++是非线程安全的,如何解决呢?

  • 在方法上加上synchronized

    public synchronized void addPlusPlus() {
        number ++;
    }

引入synchronized关键字后,保证了该方法每次只能够一个线程进行访问和操作,最终输出的结果也就为20000。

  • 为了解决n++,引入重量级的同步机制,有种杀鸡焉用牛刀的感觉。

    我们还可以使用JUC下面的原子包装类,即int类型的number,可以使用AtomicInteger来代替

    //创建一个原子Integer包装类,默认为0
    AtomicInteger number = new AtomicInteger();
    
    public void addAtomic(){
        number.getAndIncrement();    //相当于number++
    }

你可能感兴趣的:(java后端juc)