多线程中的AtomicInteger

时间:2016-11-30 06:20:13

标签: java multithreading atomicinteger

我想找到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!

线程等于数字变量:

 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多线程安全,但为什么线程的数值相等?

4 个答案:

答案 0 :(得分:9)

在(2)情况下,最多11个线程(来自ExecutorService的10个加上主线程)争夺对AtomicInteger的访问权,而在情况(1)中只有主线程线程访问它。事实上,对于案例(1),您可以使用int代替AtomicInteger

AtomicInteger类使用CAS个寄存器。它通过读取值,执行增量,然后使用寄存器中的值交换值来实现此目的,如果它仍然具有与最初读取的值相同(比较和交换)。如果另一个线程更改了它重新启动的值,则重新开始:read - increment - compare-and-swap,直到它成功。

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

<强> 修改

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

答案 1 :(得分:4)

你的外环是:     while (numbers.get() < 1000000)

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

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

(正如其他人所说,你显然增加了争用的数量,但我怀疑额外的工作线程是你所看到的减速中的一个更重要的因素。)

关于你的第二个问题,我很确定两个子线程的AtomicInteger合约是否违反了getAndIncrement的相同值。所以我必须从你的代码示例中看到其他东西。可能是你看到两个单独的程序运行的输出?

答案 2 :(得分:1)

  

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

仅仅因为你在run()内进行。因此,多个线程会同时尝试执行此操作,因此会有 等待 发布 。鲍莫尔给出了一个低级别的解释。

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

  

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

我认为没有任何可能发生这种情况。如果出现这种情况,应该从0开始。

答案 3 :(得分:0)

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