我有一个类Serializer:
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%的吞吐量。当我用同步块实现这个排队/发送时,基准测试给出50%的直接情况,因为JVM优化了同步;这将是伟大的,但它也不会扩展。使用juc.Lock标度但遭受与上述代码类似的单线程吞吐量降级。如果我没有弄错,一旦JVM优化了同步,它仍然必须使用一些防护,以防该方法再次被同时调用并放回锁定。
我的问题是,如何通过锁定,队列或其他序列化逻辑实现类似的效果,即,当没有并发呼叫时,具有廉价且快速的路径,并且还有另一条路径用于concurret案例,因此代码可以扩展并保持快速用于单线程使用。
答案 0 :(得分:1)
正如其他人所说,synchronized
已经为您提供了一个相当有效的工具。所以使用不同的东西不应该是希望获得更好的表现。
但是,如果你的意图不是像你在问题中所说的那样阻止来电者,那么这是合法的。 (虽然这可能意味着生活的表现较差)。
在查看使用队列和原子整数的尝试时,我看到的第一件事是,在没有待处理项目且没有其他消费者正在运行的情况下,可以绕过队列。在低争用的情况下可能会减少开销:
final ConcurrentLinkedQueue<T> queue;
final AtomicInteger wip;
@Override public void accept(T t) {
if(wip.compareAndSet(0, 1)) { // no contention?
actual.accept(t);
if(wip.decrementAndGet()==0) return; // still no contention
}
else {
if(!queue.offer(t))
throw new AssertionError("queue should be unbounded");
if(wip.getAndIncrement() != 0) return; // other consumer running
}
do {
actual.accept(queue.poll());
} while (wip.decrementAndGet() > 0);
}
答案 1 :(得分:0)
这就是synchronized已经为你做的事情。 (它在其他策略中使用偏向锁定)
如果您要问的是有更有效的方式以一般方式同步数据,那么如果您能够在这方面考虑数十年的专业知识,我会感到非常惊讶。如果您确实找到了一种更有名的方法,那么可能就是&#34; kd304锁定策略&#34;。
如果你问是否有办法减少特定/特殊情况代码的开销,那肯定有,但这取决于你在做什么,这个问题不明确。