【问题标题】:Is there a better AtomicInteger compare feature for race conditions?竞争条件是否有更好的 AtomicInteger 比较功能?
【发布时间】:2014-02-24 14:44:16
【问题描述】:

我正在尝试为现有类编写一个装饰器,如果没有足够的可用资源,该类会拒绝。这是一个完全没有多线程的代码示例版本:

public interface Handler {
    boolean handleTask(Task myTask); // returns true if successful, false if failure
}

public interface Task {
    int getResources();
    // Among other things
}

public BlockingHandler implements Handler {
    private int occupied;
    private final int limit;

    private final Handler backingHandler;

    BlockingHandler(Handler backingHandler) {
        this.backingHandler = backingHandler;
    }

    @Override
    public boolean handleTask(Task myTask) {
        if(myTask.getResources() + occupied > limit) return false;
        occupied += myTask.getResources();
        return backingHandler.handleTask(myTask);
    }

    // Don't worry about this part, I'm not doing it this way, I just want the code to make sense
    public void notifyResourceRelease(Task finishedTask) {
        if(finishedTask.isDone()) occupied -= myTask.getResources();
    }
}

问题是,这个handleTask 方法可以在多个线程上调用,而且我想要非常快(即避免synchronized)。将occupied 设为volatileAtomicInteger 是不够的,因为仍然可能存在竞争条件,例如:

Thread 1: call handleTask
Thread 1: call atomicOccupied.get()
Thread 2: call handleTask
Thread 1: evaluate if condition
Thread 2: call atomicOccupied.get()

不使用synchronized 是否可以做到这一点?例如,是否有一个扩展的AtomicInteger 类具有更强大的compareAndSet

【问题讨论】:

  • 听起来像是semaphore 的工作。
  • 为什么不使用 ArrayBlockingQueue?该类是为这种类型的功能而构建的
  • 我相信这个问题已经得到解答。 stackoverflow.com/questions/4210292/…
  • @durron597 你可以使用if (!semaphore.tryAcquire()) throw new SomeException();。它本质上是一个 AtomicInteger,所以我会使用它而不是手动重新实现一个非常相似的逻辑。
  • @assylias 没关系,我记错了Semaphore 的工作原理,我记得.acquire() 但不记得.tryAcquire()

标签: java multithreading synchronization race-condition


【解决方案1】:

是否可以在不使用同步的情况下做到这一点?例如,是否存在具有更强大 compareAndSet 的扩展 AtomicInteger 类?

您应该能够在没有 synchronized 块的情况下执行此操作。不,没有更多的扩展 AtomicInteger 类。

如果我了解您的要求,您可以在 while 循环中执行此操作。比如:

final AtomicInteger occupied = new AtomicInteger();
...
int prev;
int numResources = myTask.getResources();
do {
    prev = occupied.get();
    if (numResources + prev > limit) return false;
} while (!occupied.compareAndSet(prev, prev + numResources));

如果可能,此循环将旋转更新占用。如果达到限制,它将返回 false。您需要旋转,因为如果其他线程在您获得前一个值并且您去调整值之间更新了占用计数,那么您将需要循环并再次获取占用计数。这是典型的模式。


另外,您需要使用AtomicInteger 而不是volatile,因为您使用的是compareAndSet。有关详细信息,请参阅此问题:

What is the difference between using a volatile primitive over atomic variables?

【讨论】:

  • 我想过这个,但我不确定它是否比synchronized更快
  • 在大多数情况下,它会比阻止@durron597 更快。唯一的例外是如果有大量线程并且占用的更新竞争激烈。即使这样,它也可能胜过synchronized,但阻塞队列或其他东西可能会胜过两者。
  • @durron597 考虑synchronized 至少进行一次易失性读取(AtomicInteger.get 和一个易失性集AtomicInteger.set)。这肯定会更好。
  • 我认为答案是“是的,它是必需的”,但我只是想检查一下:AtomicInteger 仍然需要还是 volatile 就足够了?
  • 使用 AtomicInteger 并将其声明为 final。它包装了一个 volatile 字段(带有一些 CAS/原子指令),但您不必担心它。基元的可变增量不是原子的,原因有很多解释。
猜你喜欢
  • 2011-09-16
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 2018-09-25
  • 2011-04-03
  • 2017-10-25
  • 1970-01-01
  • 2021-12-06
相关资源
最近更新 更多