【发布时间】:2017-12-29 09:21:42
【问题描述】:
目前我在我的应用程序中使用AtomicLong 作为同步计数器,但我发现它具有高并发/争用,例如由于明显的原因(例如并发 CAS),使用 8 个线程,我的吞吐量比单线程低得多(低 75%)。
用例: 一个计数器变量
- 由多个线程同时更新
- 具有高写入争用,基本上线程中的每次使用都由写入和之后立即读取组成
- 要求是每次从计数器读取(在写入后立即)都获得一个唯一的递增值。 并不要求每个检索到的计数器值都以与不同线程(写入器)递增值的顺序相同的顺序递增。
所以我尝试将AtomicLong 替换为LongAdder,实际上,从我的测量结果来看,我的 8 线程吞吐量要好得多 - (仅)比单线程低 20%(相比 75% )。
但是我不确定我是否正确理解LongAdder 的工作方式。
JavaDoc 说:
当多线程时,这个类通常比 AtomicLong 更可取 更新用于收集等目的的共同金额 统计,不适用于细粒度的同步控制。
对于sum()
返回当前总和。返回的值不是原子快照; 在没有并发更新的情况下调用返回一个准确的 结果,但是在求和时发生的并发更新 计算可能不会被合并。
什么是细粒度同步控制...
通过查看this so question 以及AtomicLong 和Striped64 的来源,我想我理解如果AtomicLong 上的更新由于另一个线程发出的CAS 指令而被阻止,则更新存储在线程本地并在以后积累以获得一些最终的一致性。所以没有进一步的同步,因为LongAdder中的incrementAndGet()不是原子的,而是两条指令,我担心以下是可能的:
private static final LongAdder counter = new LongAdder(); // == 0
// no further synchronisation happening in java code
Thread#1 : counter.increment();
Thread#2 : counter.increment(); // CAS T#1 still ongoing, storing +1 thread-locally
Thread#2 : counter.sum(); // == 1
Thread#3 : counter.increment(); // CAS T#1 still ongoing, storing +1 thread-locally
Thread#3 : counter.sum(); // == 1
Thread#1 : counter.sum(); // == 3 (after merging everything)
如果可能,AtomicLong 并不真正适合我的用例,这可能算作“细粒度同步控制”。
然后用我的 write/read^n 模式我可能不能做得比 AtomicLong 更好?
【问题讨论】:
标签: java multithreading thread-safety increment