我正在阅读布鲁斯·埃克尔的 Thinking in Java,4th edition 中的并发性。以下是本书的基本示例代码,用于演示同步的必要性。
//SerialNumberGenerator.java
public class SerialNumberGenerator {
private static volatile int serialNumber = 0;
public static int nextSerialNumber() {
return serialNumber++; // Not thread-safe
}
}
//: concurrency/SerialNumberChecker.java
// Operations that may seem safe are not,
// when threads are present.
import java.util.concurrent.*;
// Reuses storage so we don’t run out of memory:
class CircularSet {
private int[] array;
private int len;
private int index = 0;
public CircularSet(int size) {
array = new int[size];
len = size;
// Initialize to a value not produced
// by the SerialNumberGenerator:
for(int i = 0; i < size; i++)
array[i] = -1;
}
public synchronized void add(int i) {
array[index] = i;
// Wrap index and write over old elements:
index = ++index % len;
}
public synchronized boolean contains(int val) {
for(int i = 0; i < len; i++)
if(array[i] == val) return true;
return false;
}
}
public class SerialNumberChecker {
private static final int SIZE = 10;
private static CircularSet serials =
new CircularSet(1000);
private static ExecutorService exec =
Executors.newCachedThreadPool();
static class SerialChecker implements Runnable {
public void run() {
while(true) {
int serial =
SerialNumberGenerator.nextSerialNumber();
if(serials.contains(serial)) {
System.out.println("Duplicate: " + serial);
System.exit(0);
}
serials.add(serial);
}
}
}
public static void main(String[] args) throws Exception {
for(int i = 0; i < SIZE; i++)
exec.execute(new SerialChecker());
// Stop after n seconds if there’s an argument:
if(args.length > 0) {
TimeUnit.SECONDS.sleep(new Integer(args[0]));
System.out.println("No duplicates detected");
System.exit(0);
}
}
}
他在书中提到的输出是这样的任何数字:
Duplicate: 8468656
当我运行代码时,我得到了输出:
Duplicate: 3484
Duplicate: 3485
我知道该程序是线程不安全的,数字可能不同但是,为什么我在这里得到2个重复的连续值?这怎么可能?
任何人都可以解释(低级细节)上述程序中重复数字生成的过程吗?
答案 0 :(得分:1)
陈述
System.out.println("Duplicate: " + serial);
System.exit(0);
不要阻止其他线程在中间执行操作。因此,如果您运行 n 线程,所有调用不安全的代码,从而可能执行这两个语句,可能有最多 n 个线程在其中一个线程之前打印其消息设法执行System.exit(0);
答案 1 :(得分:0)
它来自SerialNumberGenerator。用AtomicInteger的实例替换int值,你永远不应该重复。
啊。霍尔格写的是对的。答案 2 :(得分:0)
好吧,Holger的答案可以解释2个副本,总的来说他解释了线程不安全代码导致各种不确定的可能性。我同意这一点。但是,我发布的低级详细信息可以帮助解释由于上述代码可能产生的众多可能性之一。
javap -c SerialNumberGenerator.class
编译自&#34; SerialNumberGenerator.java&#34;
public class SerialNumberGenerator {
public SerialNumberGenerator();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static int nextSerialNumber();
Code:
0: getstatic #2 // Field serialNumber:I
3: dup
4: iconst_1
5: iadd
6: putstatic #2 // Field serialNumber:I
9: ireturn
static {};
Code:
0: iconst_0
1: putstatic #2 // Field serialNumber:I
4: return
}
正如你在这里看到的,iadd和ireturn之间有一条指令。可能的是,当一个线程加载serialNumber
的值并阻塞另一个线程时,它也会加载它,当其中一个线程递增它并将其添加到队列中时。保持未增加值的其他线程现在将其递增以获得相同的值,并且对数组的检查返回&#34; Duplicate&#34;。然后,霍尔格说,
陈述
System.out.println("Duplicate: " + serial);
System.exit(0);
不要阻止其他线程在中间执行操作。可能一个线程在执行退出之前被阻塞(在打印println语句之后),而另一个线程也执行了prinln语句。当调度程序返回到第一个线程时,它退出。这只是许多其他人的典型可能性。目的是可视化可能导致输出的过程,以使图像更清晰。
答案 3 :(得分:0)
尝试使用:
public static synchronized int nextSerialNumber()
{
return serialNumber++;
}
这个'同步'解决了你的问题
但你应该使用java.util.concurrent.atomic.AtomicInteger
- 更好的解决方案。