我想找到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多线程安全,但为什么线程的数值相等?
答案 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以避免这种情况。