[英]AtomicInteger in multithreading
我想找出从 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 多线程安全,线程仍然获得相同的 number 值?
在 (2) 情况下,多达 11 个线程(来自ExecutorService
的 10 个线程加上主线程)正在竞争对AtomicInteger
访问,而在情况 (1) 中,只有主线程才能访问它。 事实上,对于情况 (1),您可以使用int
而不是AtomicInteger
。
AtomicInteger
类使用CAS寄存器。 它通过读取值,执行增量,然后将值与寄存器中的值交换(如果它仍然具有最初读取的相同值(比较和交换))来完成此操作。 如果另一个线程改变了它重新开始的值:读取 - 增量 - 比较和交换,直到它成功。
优点是这是无锁的,因此可能比使用锁更快。 但它在激烈的竞争下表现不佳。 更多的争用意味着更多的重试。
编辑
正如@teppic 指出的那样,另一个问题使案例 (2) 比案例 (1) 慢。 随着发布的作业中数字的增加,循环条件保持为真的时间比所需的时间长得多。 当 executor 的所有 10 个线程都在忙着确定它们给定的数字是否是素数时,主线程不断向 executor 发布新作业。 这些新工作在前面的工作完成之前没有机会增加数字。 因此,当它们在队列中时, numbers
不会增加,并且主线程可以同时完成一个或多个循环,发布新作业。 最终结果是,可以创建和发布的工作岗位比所需的 1000000 个岗位多得多。
你的外循环是: while (numbers.get() < 1000000)
这允许您继续向主线程中的 ExecutorService 提交比预期更多的 Runnable。
您可以尝试将循环更改为: for(int i=0; i < 1000000; i++)
(正如其他人提到的,您显然增加了争用数量,但我怀疑额外的工作线程是您所看到的减速的更大因素。)
至于你的第二个问题,我很确定两个子线程看到相同的getAndIncrement值是违反AtomicInteger的约定的。 因此,我在您的代码示例中没有看到的其他事情一定会发生。 可能是您看到了程序两次单独运行的输出?
请解释为什么选项 (2) 更慢?
仅仅是因为您在run()
。 因此,多个线程将尝试同时执行此操作,因此会有wait s 和release s。 Bowmore 给出了一个低层次的解释。
在(1)中它是顺序的。 所以不会出现这样的情况。
尽管 AtomicInteger 多线程安全,为什么线程获得相同的 number 值?
我不认为有任何可能发生这种情况。 如果有这种情况,它应该从 0 发生。
您在这里错过了两个要点:AtomicInteger 的用途以及多线程一般如何工作。
关于为什么选项 2 较慢,@bowmore 已经提供了一个很好的答案。
现在关于打印相同的数字两次。 AtomicInteger 就像任何其他对象一样。 您启动线程,它们会检查此对象的值。 由于它们与您的主线程竞争,这会增加计数器,因此两个子线程仍可能看到相同的值。 我会向每个 Runnable 传递一个 int 以避免这种情况。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.