为什么这个 Thread 代码有时会打印出错误的意外结果?
Why this Thread code print wrong unpredicted result sometimes?
我是java初学者,第一次使用线程。
class Counter2 {
private int value = 0;
public void increment() {
value++;
printCounter();
}
public void decrement() {
value--;
printCounter();
}
public void printCounter() {
System.out.println(value);
}
}
class MyThread3 extends Thread {
Counter2 sharedCounter;
public MyThread3(Counter2 c) {
this.sharedCounter = c;
}
public void run() {
int i = 0;
while (i <= 100) {
sharedCounter.increment();
sharedCounter.decrement();
try {
sleep((int) (Math.random() * 2));
} catch (InterruptedException e) {
}
// System.out.println(i);
i++;
}
}
}
public class MyTest {
public static void main(String[] args) {
Thread t1, t2;
Counter2 c = new Counter2();
t1 = new MyThread3(c);
t1.start();
t2 = new MyThread3(c);
t2.start();
}
}
这段代码有2个线程和1个线程共享的计数器。线程只是重复加 1,减 1 到计数器值。所以,如果我猜的话,结果应该是0。因为初始值为0,递增和递减的数量是相同的。但有时最后打印的数字不是0,而是-1或-2等。请解释这是为什么。
这里有两个问题。它们是并发的原子性和可见性方面。自增和自减都是复合动作,需要在多线程环境下以原子方式执行。除此之外,无论何时读取计数器,您都不应该读取陈旧的值。 None 您当前的实施保证了这些。
回到解决方案,实现此目的的一种简单方法是使用同步方法,该方法使用当前实例上的锁来实现线程安全。但这样做的成本相当高,并且会产生更多的锁定开销。
更好的方法是使用基于 CAS 的非阻塞同步来完成手头的任务。这是它在实践中的样子。
class Counter2 {
private LongAdder value = new LongAdder();
public void increment() {
value.increment();;
printCounter();
}
public void decrement() {
value.decrement();;
printCounter();
}
public void printCounter() {
System.out.println(value.intValue());
}
}
由于您是初学者,我建议您阅读这本好书 Java Concurrency in Practice 1st Edition,它以我们这个时代的一些伟大作家所著,以非常优美、易懂的方式解释了所有这些基础知识!如果您对本书的内容有任何疑问,也欢迎您在这里 post 提问。从头到尾至少读两遍!
更新
CAS 即所谓的 ComparaAndSwap 是一种通过使用低级 CPU 指令实现的无锁同步方案。这里它读取 increment/decrement 之前的计数器值,然后在更新时检查初始值是否仍然存在。如果是这样,它会成功更新值。否则,另一个线程可能同时更新计数器的值,因此 increment/decrement 操作失败并再次重试。
正确。
AtomicInteger
我更喜欢的替代解决方案是使用 Atomic…
classes。具体在这里,AtomicInteger
。 class 是一个线程安全的整数包装器。
将您的成员字段从 Counter2 sharedCounter;
更改为 AtomicInteger sharedCounter;
。然后在 class 上使用各种方法来递增、递减和查询当前值。
然后您可以完全丢弃 Counter2
class。
执行者
此外,您应该知道,在现代 Java 中,我们很少需要直接解决 Thread
class。相反,我们使用添加到 Java 5.
的执行器框架
将您的任务定义为 Runnable
或 Callable
。无需从 Thread
.
扩展
查看 Oracle 教程,并在此处搜索 Stack Overflow 上的现有帖子。
我是java初学者,第一次使用线程。
class Counter2 {
private int value = 0;
public void increment() {
value++;
printCounter();
}
public void decrement() {
value--;
printCounter();
}
public void printCounter() {
System.out.println(value);
}
}
class MyThread3 extends Thread {
Counter2 sharedCounter;
public MyThread3(Counter2 c) {
this.sharedCounter = c;
}
public void run() {
int i = 0;
while (i <= 100) {
sharedCounter.increment();
sharedCounter.decrement();
try {
sleep((int) (Math.random() * 2));
} catch (InterruptedException e) {
}
// System.out.println(i);
i++;
}
}
}
public class MyTest {
public static void main(String[] args) {
Thread t1, t2;
Counter2 c = new Counter2();
t1 = new MyThread3(c);
t1.start();
t2 = new MyThread3(c);
t2.start();
}
}
这段代码有2个线程和1个线程共享的计数器。线程只是重复加 1,减 1 到计数器值。所以,如果我猜的话,结果应该是0。因为初始值为0,递增和递减的数量是相同的。但有时最后打印的数字不是0,而是-1或-2等。请解释这是为什么。
这里有两个问题。它们是并发的原子性和可见性方面。自增和自减都是复合动作,需要在多线程环境下以原子方式执行。除此之外,无论何时读取计数器,您都不应该读取陈旧的值。 None 您当前的实施保证了这些。
回到解决方案,实现此目的的一种简单方法是使用同步方法,该方法使用当前实例上的锁来实现线程安全。但这样做的成本相当高,并且会产生更多的锁定开销。
更好的方法是使用基于 CAS 的非阻塞同步来完成手头的任务。这是它在实践中的样子。
class Counter2 {
private LongAdder value = new LongAdder();
public void increment() {
value.increment();;
printCounter();
}
public void decrement() {
value.decrement();;
printCounter();
}
public void printCounter() {
System.out.println(value.intValue());
}
}
由于您是初学者,我建议您阅读这本好书 Java Concurrency in Practice 1st Edition,它以我们这个时代的一些伟大作家所著,以非常优美、易懂的方式解释了所有这些基础知识!如果您对本书的内容有任何疑问,也欢迎您在这里 post 提问。从头到尾至少读两遍!
更新
CAS 即所谓的 ComparaAndSwap 是一种通过使用低级 CPU 指令实现的无锁同步方案。这里它读取 increment/decrement 之前的计数器值,然后在更新时检查初始值是否仍然存在。如果是这样,它会成功更新值。否则,另一个线程可能同时更新计数器的值,因此 increment/decrement 操作失败并再次重试。
AtomicInteger
我更喜欢的替代解决方案是使用 Atomic…
classes。具体在这里,AtomicInteger
。 class 是一个线程安全的整数包装器。
将您的成员字段从 Counter2 sharedCounter;
更改为 AtomicInteger sharedCounter;
。然后在 class 上使用各种方法来递增、递减和查询当前值。
然后您可以完全丢弃 Counter2
class。
执行者
此外,您应该知道,在现代 Java 中,我们很少需要直接解决 Thread
class。相反,我们使用添加到 Java 5.
将您的任务定义为 Runnable
或 Callable
。无需从 Thread
.
查看 Oracle 教程,并在此处搜索 Stack Overflow 上的现有帖子。