简体   繁体   English

多线程中的 AtomicInteger

[英]AtomicInteger in multithreading

I want to find out all the prime numbers from 0 to 1000000. For that I wrote this stupid method:我想找出从 0 到 1000000 的所有质数。为此我写了这个愚蠢的方法:

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

It's good for me and it doesn't need any edit .这对我有好处,不需要任何编辑 Than I wrote the following code:比我写了以下代码:

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();
    }
}

Please, pay attention to (1) and (2) marked rows.请注意 (1) 和 (2) 标记的行。 When (1) is enabled the program runs fast, but when (2) is enabled it works slower.当 (1) 启用时,程序运行得很快,但当 (2) 启用时,它运行得更慢。

The output shows small portions with large delay输出显示具有大延迟的小部分

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!

and threads get equal number value:和线程得到同等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!

Please explain me why option (2) is more slowly and why threads get equal value for number despite AtomicInteger multithreading safe?请解释为什么选项 (2) 更慢,为什么尽管 AtomicInteger 多线程安全,线程仍然获得相同的 number 值?

In the (2) case, up to 11 threads (the ten from the ExecutorService plus the main thread) are contending for access to the AtomicInteger , whereas in case (1) only the main thread accesses it.在 (2) 情况下,多达 11 个线程(来自ExecutorService的 10 个线程加上主线程)正在竞争对AtomicInteger访问,而在情况 (1) 中,只有主线程才能访问它。 In fact, for case (1) you could use int instead of AtomicInteger .事实上,对于情况 (1),您可以使用int而不是AtomicInteger

The AtomicInteger class makes use of CAS registers. AtomicInteger类使用CAS寄存器。 It does this by reading the value, doing the increment, and then swapping the value with the value in the register if it still has the same value that was originally read (compare and swap).它通过读取值,执行增量,然后将值与寄存器中的值交换(如果它仍然具有最初读取的相同值(比较和交换))来完成此操作。 If another thread has changed the value it retries by starting again : read - increment - compare-and-swap, until it is succesful.如果另一个线程改变了它重新开始的值:读取 - 增量 - 比较和交换,直到它成功。

The advantage is that this is lockless, and therefore potentially faster than using locks.优点是这是无锁的,因此可能比使用锁更快。 But it performs poorly under heavy contention.但它在激烈的竞争下表现不佳。 More contention means more retries.更多的争用意味着更多的重试。

Edit编辑

As @teppic points out, another problem makes case (2) slower than case (1).正如@teppic 指出的那样,另一个问题使案例 (2) 比案例 (1) 慢。 As the increment of numbers happens in the posted jobs, the loop condition remains true for much longer than needed.随着发布的作业中数字的增加,循环条件保持为真的时间比所需的时间长得多。 While all 10 threads of the executor are churning away to determine whether their given number is a prime, the main thread keeps posting new jobs to the executor.当 executor 的所有 10 个线程都在忙着确定它们给定的数字是否是素数时,主线程不断向 executor 发布新作业。 These new jobs don't get an opportunity to increment numbers until preceding jobs are done.这些新工作在前面的工作完成之前没有机会增加数字。 So while they're on the queue numbers does not increase and the main thread can meanwhile complete one or more loops loop, posting new jobs.因此,当它们在队列中时, numbers不会增加,并且主线程可以同时完成一个或多个循环,发布新作业。 The end result is that many more jobs can be created and posted than the needed 1000000.最终结果是,可以创建和发布的工作岗位比所需的 1000000 个岗位多得多。

Your outer loop is: while (numbers.get() < 1000000)你的外循环是: while (numbers.get() < 1000000)

This allows you to continue submitting more Runnables than intended to the ExecutorService in the main thread.这允许您继续向主线程中的 ExecutorService 提交比预期更多的 Runnable。

You could try changing the loop to: for(int i=0; i < 1000000; i++)您可以尝试将循环更改为: for(int i=0; i < 1000000; i++)

(As others have mentioned you are obviously increasing the amount of contention , but I suspect the extra worker threads are a larger factor in the slowdown you are seeing.) (正如其他人提到的,您显然增加了争用数量,但我怀疑额外的工作线程是您所看到的减速的更大因素。)

As for your second question, I'm pretty sure that it is against the contract of AtomicInteger for two child threads to see the same value of getAndIncrement.至于你的第二个问题,我很确定两个子线程看到相同的getAndIncrement值是违反AtomicInteger的约定的。 So something else must be going on which I am not seeing from your code sample.因此,我在您的代码示例中没有看到的其他事情一定会发生。 Might it be that you are seeing output from two separate runs of the program?可能是您看到了程序两次单独运行的输出?

Explain me why option (2) is more slowly?请解释为什么选项 (2) 更慢?

Simply because you do it inside run() .仅仅是因为您在run() So multiple threads will try to do it at the same time hence there will be wait s and release s.因此,多个线程将尝试同时执行此操作,因此会有wait s 和release s。 Bowmore has given a low level explanation. Bowmore 给出了一个低层次的解释。

In (1) it is sequential.在(1)中它是顺序的。 So there will be no such a scenario.所以不会出现这样的情况。

Why threads get equal value for number despite AtomicInteger multithreading safe?尽管 AtomicInteger 多线程安全,为什么线程获得相同的 number 值?

I don't see any possibility to happen this.我不认为有任何可能发生这种情况。 If there's such a case it should happen from 0.如果有这种情况,它应该从 0 发生。

You miss two main points here: what AtomicInteger is for and how multithreading works in general.您在这里错过了两个要点:AtomicInteger 的用途以及多线程一般如何工作。
Regarding why Option 2 is slower, @bowmore provided an excellent answer already.关于为什么选项 2 较慢,@bowmore 已经提供了一个很好的答案。
Now regarding printing same number twice.现在关于打印相同的数字两次。 AtomicInteger is like any other object. AtomicInteger 就像任何其他对象一样。 You launch your threads, and they check the value of this object.您启动线程,它们会检查此对象的值。 Since they compete with your main thread, that increases the counter, two child threads still may see same value.由于它们与您的主线程竞争,这会增加计数器,因此两个子线程仍可能看到相同的值。 I would pass an int to each Runnable to avoid that.我会向每个 Runnable 传递一个 int 以避免这种情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM