【问题标题】:Possible to safely increment BigInteger in a thread safe way, perhaps with AtomicReference, w/o locking?可以以线程安全的方式安全地递增 BigInteger,也许使用 AtomicReference,没有锁定?
【发布时间】:2012-12-09 23:58:52
【问题描述】:

我们的很多代码都是遗留的,但我们正在转向“大数据”后端,我正在尝试宣传更新的 API 调用,鼓励使用最新的 Spring 库等。我们的问题之一是应用层 ID 生成。由于我不明白的原因,更高的权威想要顺序的 BigInteger。我会通过重新生成并重新尝试失败的插入来使它们随机化,但我确实被否决了。

撇开抱怨不谈,我现在需要跨线程递增并获取 BigInteger 并以安全且高性能的方式进行操作。我以前从未使用过 AtomicReference,但它看起来非常适合这个应用程序。现在我们有一个同步的代码块,它严重地损害了我们的性能。

这是正确的方法吗?语法示例?

我应该提到这个模块的工作方式,它使用存储过程访问数据库以获取要使用的值范围。一次数以万计,因此它可能仅在 20 分钟内发生一次。这可以防止各种服务器相互踩踏,但也增加了必须将 BigInteger 设置为任意后续值的麻烦。当然,这也需要是线程安全的。

附:我仍然认为我的随机生成想法比处理所有这些线程的东西要好。 BigInteger 是一个大得离谱的数字,两次生成相同数字的几率必须接近于零。

【问题讨论】:

  • AtomicReference 似乎不会在没有锁定的情况下为您做这件事。但是,除非您需要大于 long 的整数,否则如果 BigInteger 没有减慢您的速度,我会感到惊讶。尝试说服他们选择AtomicLong
  • 我会调查的。无论如何,我仍然对答案感到好奇。 ;-) 特别是因为他们可能会再次否决我。政治。
  • AtomicReference(与其他 Atomic.. 类一样)不使用锁定。
  • 关于 AtomicReference 的非锁定,我从文档中得到的印象是它在一定程度上(至少有一点)依赖于系统......所以虽然不能保证,但它是人们可能会期待什么。例如,在我古老的 32 位 Flintstonian Stone-Box-With-A-Bird-In-It 桌面开发盒上,人们可能认为处理器不够先进,无法支持它……但在部署后的实际服务器上这似乎更有可能。
  • 来自“实践中的 Java 并发”:“在最坏的情况下,如果类似 CAS 的指令不可用,JVM 会使用自旋锁。”

标签: java increment biginteger atomicreference


【解决方案1】:

使用 Java 8 中引入的accumulateAndGetgetAndAccumulate,这变得更易于管理和理解。它们允许您通过提供将值设置为函数结果的累加器函数来原子地更新值,并且还可以根据您的需要返回先前的或计算的结果。以下是该类的外观示例,然后是我编写的使用它的简单示例:

import java.math.BigInteger;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

public final class AtomicBigInteger {

  private final AtomicReference<BigInteger> bigInteger;

  public AtomicBigInteger(final BigInteger bigInteger) {
    this.bigInteger = new AtomicReference<>(Objects.requireNonNull(bigInteger));
  }

  // Method references left out for demonstration purposes
  public BigInteger incrementAndGet() {
    return bigInteger.accumulateAndGet(BigInteger.ONE, (previous, x) -> previous.add(x));
  }

  public BigInteger getAndIncrement() {
    return bigInteger.getAndAccumulate(BigInteger.ONE, (previous, x) -> previous.add(x));
  }

  public BigInteger get() {
    return bigInteger.get();
  }
}

一个使用它的例子:

import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ABIExample {

  private static final int AVAILABLE_PROCS = Runtime.getRuntime().availableProcessors();
  private static final int INCREMENT_AMOUNT = 2_500_000;
  private static final int TASK_AMOUNT = AVAILABLE_PROCS * 2;
  private static final BigInteger EXPECTED_VALUE = BigInteger.valueOf(INCREMENT_AMOUNT)
                                                             .multiply(BigInteger
                                                                           .valueOf(TASK_AMOUNT));

  public static void main(String[] args)
      throws InterruptedException, ExecutionException {
    System.out.println("Available processors: " + AVAILABLE_PROCS);


    final ExecutorService executorService = Executors
        .newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    final AtomicBigInteger atomicBigInteger = new AtomicBigInteger(BigInteger.ZERO);

    final List<Callable<Void>> incrementTasks =  IntStream.rangeClosed(1, TASK_AMOUNT)
             .mapToObj(i -> incrementTask(i, atomicBigInteger))
             .collect(Collectors.toList());
    final List<Future<Void>> futures = executorService.invokeAll(incrementTasks);
    for (Future<Void> future : futures) {
      future.get();
    }
    executorService.shutdown();
    executorService.awaitTermination(30, TimeUnit.SECONDS);
    System.out.println("Final value: " + atomicBigInteger.get());
    final boolean areEqual = EXPECTED_VALUE.equals(atomicBigInteger.get());
    System.out.println("Does final value equal expected? - " + areEqual);
  }

  private static Callable<Void> incrementTask(
      final int taskNumber,
      final AtomicBigInteger atomicBigInteger
  ) {
    return () -> {
      for (int increment = 0; increment < INCREMENT_AMOUNT; increment++) {
        atomicBigInteger.incrementAndGet();
      }
      System.out.println("Task #" + taskNumber + " Completed");
      return null;
    };

  }
}

以及在我的机器上运行示例的输出:

Available processors: 8
Task #3 Completed
Task #8 Completed
Task #7 Completed
Task #6 Completed
Task #5 Completed
Task #2 Completed
Task #4 Completed
Task #1 Completed
Task #9 Completed
Task #10 Completed
Task #11 Completed
Task #13 Completed
Task #16 Completed
Task #12 Completed
Task #14 Completed
Task #15 Completed
Final value: 80000000
Does final value equal expected? - true

【讨论】:

  • 我理解上一个答案,但不是这个。我还没有太多机会使用“->”,所以这可能是也可能不是原因。 “previous”和“x”从何而来?
  • @user447607 Java 8 引入了 lambda 表达式。 Oracle here that is more comprehensive than I can explain 有一个教程。我建议通过那个。基本上,AtomicReference 上的*accumulate* 方法允许您传入BinaryOperator,它以原子方式调用该方法。 BinaryOperator@FunctionalInterfacepreviousx 是被调用方法的参数。在上面的例子中,两个参数都被传递到方法中。
  • @mkobit:我认为你的增量方法可以用getAndUpdate/updateAndGetbigInteger.getAndUpdate(BigInteger.ONE::add)写得更清楚
  • @Lii 可能!我试图“反映”已接受的答案,以显示两者之间的对比,并尝试展示*andGet*andAccumulate 类型方法之间的区别。我同意你的观点,这个类的 API 可以通过你建议的方式改进,也可以通过其他方法改进。
  • 即使使用accumulateAndGet,代码也会变得更简单bigInteger.accumulateAndGet(BigInteger.ONE, BigInteger::add)
【解决方案2】:

可以使用 AtomicReference,这是一个快速草稿:

public final class AtomicBigInteger {

    private final AtomicReference<BigInteger> valueHolder = new AtomicReference<>();

    public AtomicBigInteger(BigInteger bigInteger) {
        valueHolder.set(bigInteger);
    }

    public BigInteger incrementAndGet() {
        for (; ; ) {
            BigInteger current = valueHolder.get();
            BigInteger next = current.add(BigInteger.ONE);
            if (valueHolder.compareAndSet(current, next)) {
                return next;
            }
        }
    }
}

它基本上是 incrementAndGet() 的 AtomicLong 代码的副本

【讨论】:

  • 在写我的时候没有注意到这个答案:)
  • 一直发生在我身上:)
  • 是的,我只是在看那个代码,在这里:docjar.com/html/api/java/util/concurrent/atomic/…
  • 现在我突然想到,我可能需要在 AtomicReference 中使用 AtomicLong... 问题是我还必须以线程安全的方式设置值,因为偶尔会调用 SP 来获取新的使用的值范围。
  • 不。对 API 的审查表明它具有 AtomicReference 所做的所有必需的 get/set 例程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
  • 2012-05-27
  • 1970-01-01
  • 2022-01-10
  • 1970-01-01
相关资源
最近更新 更多