使用的示例太糟糕了,无法演示内存一致性问题。让它发挥作用将需要脆弱的推理和复杂的编码。然而你可能看不到结果。多线程问题是由于不幸的时机而发生的。如果有人想增加观察问题的机会,我们需要增加倒霉时机的机会。
下面的程序就实现了。
public class ConsistencyIssue {
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Increment(), "Thread-1");
Thread thread2 = new Thread(new Increment(), "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter);
}
private static class Increment implements Runnable{
@Override
public void run() {
for(int i = 1; i <= 10000; i++)
counter++;
}
}
}
执行 1 输出:10963,
执行2输出:14552
最终计数应该是 20000,但它比这少。原因是count++是多步操作,
1.阅读次数
2.递增计数
3. 保存
两个线程可能一次读取计数 1,将其增加到 2。然后写出 2。但如果是串行执行,则应该是 1++ -> 2++ -> 3。
我们需要一种方法来使所有 3 个步骤都原子化。即一次只能由一个线程执行。
解决方案 1:同步
用 Synchronized 包围增量。由于计数器是静态变量,因此您需要使用类级同步
@Override
public void run() {
for (int i = 1; i <= 10000; i++)
synchronized (ConsistencyIssue.class) {
counter++;
}
}
现在输出:20000
解决方案 2:AtomicInteger
public class ConsistencyIssue {
static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Increment(), "Thread-1");
Thread thread2 = new Thread(new Increment(), "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.get());
}
private static class Increment implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10000; i++)
counter.incrementAndGet();
}
}
}
我们可以使用信号量,也可以使用显式锁定。但是对于这个简单的代码 AtomicInteger 就足够了