i++在单核CPU下是否有并发安全性问题

584 阅读1分钟

关于影响线程安全的三大问题:可见性,原子性,有序性我们都很了解 一般在讲原子性问题的时候, 会扔出这么一段代码

public class CPUTest {
    private int i = 0;

    private void incr(){
        i++;
    }

    private void getI(){
        System.out.println(i);
    }


    public static void main(String[] args) throws InterruptedException {
        CPUTest cpuTest = new CPUTest();
        CountDownLatch cdl = new CountDownLatch(1);
        new Thread(){
            @Override
            public void run() {
                try {
                    cdl.await();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 5000; i++) {
                    cpuTest.incr();
                }
                System.out.println("线程1完事");
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                try {
                    cdl.await();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 5000; i++) {
                    cpuTest.incr();
                }
                System.out.println("线程2完事");
            }
        }.start();
        cdl.countDown();
        TimeUnit.SECONDS.sleep(5);
        cpuTest.getI();
    }
}

结果跑不出10000, 就算有线程安全问题,具体原因一般说这是因为i++不是原子操作,并且有可见性问题。 得到的结论大概如下:

因为i++不是原子操作, 所以在多线程下, 可能a,b两个线程读到的i都是3, a线程计算3+1=4后先将4暂存到工作内存,b线程计算出3+1=4后讲4暂存到工作内存。a线程刷新m到主内存, b线程刷新m到主内存,经过两个i++, 结果i的值只加了1,所以原子操作引发线程安全性问题

是不是很清晰?但我还是有以下问题

  • 工作内存在现实中到底是什么?
  • 如果工作内存对应的是JMM的CPU缓存,那如果在只有一个CPU的情况下,i++有没有问题? 于是我把代码上传到单核的虚拟机上,与本地做比较:
  • 本地跑的结果一直在6000左右
  • 在虚拟机下同样代码多次跑出10000 根据这个结果我们可以详细的解释导致i++线程安全性问题的原因
  1. 在JMM中,CPU缓存大致代表工作内存
  2. 在单核多线程的情况下,执行i++即使有线程切换,由于操作的是一份工作内存(缓存),所以i++没有问题
  3. 在多核多线程情况下,才有线程安全性问题