# count++与count+=1区别

没什么区别, 都不具备原子性.

# 多线程跑count++会有什么问题

在n个线程跑count+=1, 众所周知, 结果肯定和单线程运算n次不一样, 会比单线程运算n的结果要少.

这其中主要包括两个原因: 可见性和原子性.

可见性: 由于多核CPU缓存原因, 导致存在缓存与内存中数据不一致的问题, count先从内存中读到了各自CPU的缓存中, 运算后, 同样写入到各自缓存中, 而此时CPU缓存与内存同步不是实时的, 因此才会出现不一致的问题. 对此在Java中可以使用volatile修饰field, 确保其可见性, 强制从内存中读取和写入数据, 但同时性能也会下降几个数量级. 具体实现应该是利用的处理器提供的两个内存屏障指令(memory barrier).

原子性: count++或者count+=1都是不具备原子性的, 因为它们在系统层面不属于一个最小的操作单元. 其包括3个步骤: 1.将内存中count值读取出来 2.操作加+1 3.将结果写入内存 按照上面步骤, 多线程并行时, 会出现一个线程写入内存覆盖另一个线程之前写入的值. 要解决此问题, 必须将count++或者count+=1变成同步操作, 可以使用synchronized同步块或者atomic变量, 也可以在线程中各自计算结果, 最后相加.

// 示例代码
public class ThreadTest {
    private long count = 0;
    private static final long max = 1000000000;
    private void add10K() {
        int idx = 0;
        while(idx++ < max) {
            count += 1;
        }
    }
    public static long calc() throws InterruptedException {
        final ThreadTest threadTest = new ThreadTest();
        Thread th1 = new Thread(threadTest::add10K);
        Thread th2 = new Thread(threadTest::add10K);
        th1.start();
        th2.start();
        th1.join();
        th2.join();
        return threadTest.count;
    }
    public static void main(String[] args) throws InterruptedException{
        long count = ThreadTest.calc();
        System.out.println(count);
    }
}

# FAQ

# 循环10亿次时, 为什么结果值只有898741597?

count用volatile修饰时, 会出现如题的结果, 如果保证count的线程安全, 结果应该是20亿, 而现在的结果都没有达到单线程10亿次. 使用volatile解决了可见性问题, 但由于原子性问题, 仍然无法达到正确的数值, 而此时结果没有达到单线程10亿的值, 完全是因为运气问题- -! 要出现count<单线程跑的值, 肯定是由于在某一个极小时间范围中, 出现走得快线程写入内存中的值被走得慢线程计算的值所覆盖, 导致走得快线程拿到了一个小于上次计算的值, 这种情况在使用了volatile后, 由于线程读写都是直接操作内存, 所以会频繁出现, 因此容易导致最终结果达不到单线程10亿次的结果.

不使用volatile的情况下, 基本没出现过<单线程10亿次的结果, 这主要是因为在所有过程中, 基本没有缓存与主内存进行数据同步(这一块的知识点很多, 需要了解JMM和cpu缓存机制, 包括失效队列等), 也就是基本都是操作各自缓存中的数据, 而结果不是单线程10亿次, 则是因为两个线程不是同时启动的.

# 为什么循环1万次结果是接近2万?

main线程中start()两个线程启动有延迟, 或者说多个线程并不是同时启动的.

修改于: 11/22/2020, 1:57:57 AM