检测方法的并发调用

时间:2014-05-18 16:30:23

标签: java multithreading concurrency lock-free optimistic-concurrency

我有一个类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案例,因此代码可以扩展并保持快速用于单线程使用。

2 个答案:

答案 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;。

如果你问是否有办法减少特定/特殊情况代码的开销,那肯定有,但这取决于你在做什么,这个问题不明确。