【问题标题】:AtomicInteger in multithreading多线程中的 AtomicInteger
【发布时间】:2017-04-14 09:01:52
【问题描述】:

我想找出从 0 到 1000000 的所有素数。为此我写了这个愚蠢的方法:

public static boolean isPrime(int n) {
    for(int i = 2; i < n; i++) {
        if (n % i == 0)
            return false;
    }
    return true;
}

这对我有好处,而且它不需要任何编辑。比我写的代码如下:

private static ExecutorService executor = Executors.newFixedThreadPool(10);
private static AtomicInteger counter = new AtomicInteger(0);
private static AtomicInteger numbers = new AtomicInteger(0);

public static void main(String args[]) {
    long start = System.currentTimeMillis();
    while (numbers.get() < 1000000) {
        final int number = numbers.getAndIncrement();  // (1) - fast
        executor.submit(new Runnable() {
            @Override
            public void run() {
  //               int number = numbers.getAndIncrement(); // (2) - slow
                if (Main.isPrime(number)) {
                    System.out.println("Ts: " + new Date().getTime() + " " + Thread.currentThread() + ": " + number + " is prime!");
                    counter.incrementAndGet();
                }
            }
        });
    }
    executor.shutdown();
    try {
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        System.out.println("Primes: " + counter);
        System.out.println("Delay: " + (System.currentTimeMillis() - start));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

请注意 (1) 和 (2) 标记的行。启用 (1) 时,程序运行速度快,但启用 (2) 时,运行速度较慢。

输出显示延迟较大的小部分

Ts: 1480489699692 Thread[pool-1-thread-9,5,main]: 350431 is prime!
Ts: 1480489699692 Thread[pool-1-thread-6,5,main]: 350411 is prime!
Ts: 1480489699692 Thread[pool-1-thread-4,5,main]: 350281 is prime!
Ts: 1480489699692 Thread[pool-1-thread-5,5,main]: 350257 is prime!
Ts: 1480489699693 Thread[pool-1-thread-7,5,main]: 350447 is prime!
Ts: 1480489711996 Thread[pool-1-thread-6,5,main]: 350503 is prime!

线程得到相等的number值:

 Ts: 1480489771083 Thread[pool-1-thread-8,5,main]: 384733 is prime!
 Ts: 1480489712745 Thread[pool-1-thread-6,5,main]: 384733 is prime!

请解释一下为什么选项 (2) 更慢,以及为什么尽管 AtomicInteger 多线程安全,但线程的数量值相等?

【问题讨论】:

  • finality 的 int number 有什么不同吗?
  • 我在运行此程序时没有看到重复项。我通过将结果添加到 Set 来检查。
  • 不要这样做;在没有任何共享资源的情况下分块范围和进程;如果您想花哨,请使用fork-join。或者,更好的是,使用 sieve - 我敢打赌它比任何简单的多线程解决方案都要快几个数量级。
  • @ScaryWombat this final 在 Java
  • 如果您在 Executors.newFixedThreadPool(10) 中限制 10 个并发线程循环 1000000 个线程,我会感到困惑;时间总是很慢。因为它在 executor.shutdown() 之后开始工作

标签: java multithreading atomicinteger


【解决方案1】:

在 (2) 的情况下,多达 11 个线程(来自ExecutorService 的 10 个线程加上主线程)正在竞争对 AtomicInteger 的访问,而在情况 (1) 中只有主线程访问它。实际上,对于情况 (1),您可以使用 int 而不是 AtomicInteger

AtomicInteger 类使用CAS 寄存器。它通过读取值、执行增量,然后将值与寄存器中的值交换,如果它仍然具有最初读取的相同值(比较和交换)。如果另一个线程更改了值,它会重新开始重试:读取 - 递增 - 比较和交换,直到成功。

优点是这是无锁的,因此可能比使用锁更快。但它在激烈的竞争下表现不佳。更多的争用意味着更多的重试。

编辑

正如@teppic 指出的那样,另一个问题是案例 (2) 比案例 (1) 慢。随着数字的增加发生在发布的作业中,循环条件保持正确的时间比需要的时间长得多。当执行器的所有 10 个线程都在忙于确定它们给定的数字是否是素数时,主线程不断向执行器发布新作业。在完成之前的工作之前,这些新工作没有机会增加数字。因此,当它们在队列中时,numbers 不会增加,并且主线程可以同时完成一个或多个循环循环,发布新工作。最终结果是可以创建和发布比所需的 1000000 更多的工作。

【讨论】:

  • 虽然这有帮助,但在我的测试中影响很小。更大的问题是外循环快速旋转,在达到终端数量之前创建了数百万个不需要的工作。
  • @teppic (1) 和 (2) 有什么不同?
  • in 1 number 每循环递增一次。 In 2 number 在将作业移交给另一个线程所需的短暂延迟后递增。延迟比创建新作业所需的时间长,因此创建的作业比处理 1000000 个数字所需的多得多。事实证明,还有数百万。
  • @teppic 啊是的,我明白你的意思
【解决方案2】:

你的外循环是: while (numbers.get() &lt; 1000000)

这允许您继续向主线程中的 ExecutorService 提交比预期更多的 Runnables。

您可以尝试将循环更改为:for(int i=0; i &lt; 1000000; i++)

(正如其他人提到的那样,您显然增加了 争用 的数量,但我怀疑额外的工作线程是您看到的减速的更大因素。)

至于您的第二个问题,我很确定两个子线程看到相同的 getAndIncrement 值违反了 AtomicInteger 的合同。因此,我在您的代码示例中没有看到其他必须发生的事情。可能是您看到了程序两次单独运行的输出?

【讨论】:

  • 这是正确答案。它很慢,因为它在一个紧密的循环中向堆中发送垃圾邮件。
  • @BoristheSpider 不,这两种情况都不一样...当您在与getAndIncrement 相同的线程中执行while(numbers.get()...) 时,您将不会提交比预期更多的Runnables。将getAndIncrement 移动到工作线程正是允许主线程提交太多工作线程的原因。
  • @BoristheSpider 此外,我想说的是,如果他的练习的全部目的是练习使用 AtomicInteger 作为共享资源,那么争用的增加会更加明显且更加无关紧要。否则他为什么会考虑把它放在里面呢?将 getAndIncrement 移到 runnable 之外可以提高性能,但会破坏整个前提,尽管它可能是人为的。
  • @PatrickParker 我现在看到了;你是绝对正确的 - 异步增加值允许多余的工人。
【解决方案3】:

解释一下为什么选项 (2) 更慢?

仅仅是因为您在 run() 内部执行此操作。因此,多个线程会尝试同时执行此操作,因此会有 waitrelease 。鲍莫尔给出了一个低层次的解释。

在 (1) 中它是顺序的。所以不会有这样的场景。

尽管有 AtomicInteger,为什么线程的数字值相等 多线程安全吗?

我认为这不可能发生。如果有这种情况,应该从0开始。

【讨论】:

    【解决方案4】:

    您在这里忽略了两个要点:AtomicInteger 的用途以及多线程的一般工作原理。
    关于为什么选项 2 较慢,@bowmore 已经提供了一个很好的答案。
    现在关于两次打印相同的数字。 AtomicInteger 就像任何其他对象一样。你启动你的线程,它们会检查这个对象的值。由于它们与您的主线程竞争,这增加了计数器,两个子线程仍然可能看到相同的值。我会将一个 int 传递给每个 Runnable 以避免这种情况。

    【讨论】:

      猜你喜欢
      • 2017-08-23
      • 2021-08-25
      • 1970-01-01
      • 1970-01-01
      • 2019-10-06
      • 1970-01-01
      • 1970-01-01
      • 2016-04-20
      • 1970-01-01
      相关资源
      最近更新 更多