我是编程新手,研究线程已经有一段时间了。
因此,以下代码应该给出的输出为:
one 98098
two 98099
有时确实如此。
当我尝试运行它几次时,它会给出不同的输出。我可以理解JVM控制线程,我不能直接影响它,但有些输出小于98000,尽管for循环98次增加了1000。这是怎么发生的?一根线能留下线条吗?或者我做错了什么(注意:预期的输出有时会显示在屏幕上,但并不总是)
public class TestThreads {
public static void main(String [] args) {
ThreadOne t1 = new ThreadOne();
Thread one = new Thread(t1);
ThreadTwo t2 = new ThreadTwo();
Thread two = new Thread(t2);
one.start();
two.start();
}
}
class Accum {
private int counter = 0;
private static Accum a = new Accum();
private Accum() {
}
public static Accum getAccum() {
return a;
}
public int getCount() {
return counter;
}
public void updateCounter(int add) {
counter += add;
}
}
class ThreadOne implements Runnable {
Accum a = Accum.getAccum();
public void run() {
for(int x=0; x < 98; x++) {
a.updateCounter(1000);
try {
Thread.sleep(50);
} catch(InterruptedException ex) { }
}
System.out.println("one "+a.getCount());
}
}
class ThreadTwo implements Runnable {
Accum a = Accum.getAccum();
public void run() {
for(int x=0; x < 99; x++) {
a.updateCounter(1);
try {
Thread.sleep(50);
} catch(InterruptedException ex) { }
}
System.out.println("two "+a.getCount());
}
}
基本上,updateCounter
方法不是线程安全的。如果同时从两个线程调用它,可能会丢失信息。
让我们重写一下,让它更清楚地说明为什么会出现这种情况:
public void updateCounter(int add) {
// Fetch
int originalValue = counter;
// Compute
int newValue = originalValue + add;
// Store
counter = newValue;
}
想象一下,如果两个线程同时进入该方法会发生什么。我们会假装有一些";总排序";发生了什么——现实比这更复杂,但即使是简化的形式也说明了问题。假设counter
的值为5,在线程x上我们调用updateCounter(3)
,在线程y上调用updateCounter(4)
。我们可以想象这样一系列的事件:
- 线程x执行";获取";操作:
originalValue = 5
(局部变量,不受线程y影响) - 线程y执行";获取";操作:
originalValue = 5
- 线程x执行";计算";操作:
newValue = 8
- 线程y执行";计算";操作:
newValue = 9
- 线程x执行";存储";操作:
counter = 8
(注意线程x中的newValue
与线程y中的是分开的) - 线程y执行";存储";操作:
counter = 9
因此,我们最终得出counter
的值为9……就好像updateCounter(3)
调用从未发生过一样。如果最后两个操作以相反的顺序发生,则counter
将改为8。
解决此问题的方法是使用AtomicInteger
类,该类专门设计用于进行类似以下原子的操作:
class Accum {
private final AtomicInteger counter = new AtomicInteger(0);
private static Accum a = new Accum();
private Accum() {
}
public static Accum getAccum() {
return a;
}
public int getCount() {
return counter.get();
}
public void updateCounter(int add) {
counter.addAndGet(add);
}
}