我有一个类序列化器:
class Serializer<T> extends Consumer<T> {
final Consumer<? super T> actual;
// constructor omitted for brewity
@Override public synchronized void accept(T t) {
actual.accept(t);
}
}
目的是确保实际一次从单个线程运行。但是,在持有锁的同时调用回调通常是危险的,因此调用者不是持有锁,而是将传入的值排队,其中一个线程将进入,排空队列并依次循环调用实际的使用者。(另一个限制是并发调用者的数量是未知的。)
final ConcurrentLinkedQueue<T> queue;
final AtomicInteger wip;
@Override public void accept(T t) {
queue.offer(t);
if (wip.getAndIncrement() == 0) {
do {
actual.accept(queue.poll());
} while (wip.decrementAndGet() > 0);
}
}
这是可行的,并且解决了无界队列、线程跳跃和线程卡在循环中的问题,但与直接方法调用相比,基准测试在单线程情况下提供了 10% 的吞吐量。当 I. 使用同步块实现此排队/发射时,基准测试提供了 50% 的直接情况,因为 JVM 优化了同步;这会很棒,但它也不能扩展。使用 juc.Lock 可以扩展,但会遭受与上述代码类似的单线程吞吐量下降。如果我没记错的话,一旦 JVM 优化同步了,它仍然必须使用一些保护,以防该方法再次被并发调用并放回锁定。
我的问题是,如何使用锁、队列或其他序列化逻辑来实现类似的效果,即,在没有并发调用的情况下有一条便宜且快速的路径,并为并发情况提供另一条路径,所以代码可以扩展并且对于单线程使用也保持快速。