我开始学习一些java并发概念并投入使用。 但是这段代码之一超出了我的理解。
public class Count {
private int count = 0;
public synchronized void setCount(int count) {
this.count = count;
}
public synchronized int getCount() {
return count;
}
}
class CountRunner extends Thread {
Count count;
public CountRunner(Count count) {
this.count = count;
}
public void run() {
for (int i = 1; i <= 1000; i++) {
count.setCount(count.getCount() + 1);
}
}
}
class TestCount {
public static void main(String[] args) throws Exception {
Count count = new Count();
CountRunner runnerA = new CountRunner(count);
CountRunner runnerB = new CountRunner(count);
runnerA.start();
runnerB.start();
runnerA.join(); //join statement here
runnerB.join();
System.out.println("count.getCount = " + count.getCount());
}
}
Question: 1. The result is a little less than 2000 for many times, why ? 2. if delete 2 join() statement, why count.getCount = 451,even less ? 3. i think there will be no effect of deleting the join() statements, because i already have Synchronized method to lock one object to one thread each time ? So, What's the point of using Synchronized and join() ?
答案 0 :(得分:4)
这很简单。通过调用getCount + 1来调用setCount方法。在输入方法之前,运行时会计算getCount(synchronized),但是在离开getCount并输入setCount并且其他线程可以输入调用getCount时,您不会保持锁定。因此,每隔两次(或更多,取决于您创建的线程数)线程将在getCount中具有相同的值。想象一下,线程A在getCount中输入并接收值1。运行时将它执行到踏板B,它调用getCount并接收相同的值1.线程B将值设置为1并再次运行50次,因此在该阶段计数将为50。运行时产生执行到线程A,线程A调用setCount为1(记住它没有设法调用setCount并产生它的执行)。现在A将值设置为1(这是错误的)。
改变你的运行方式,如下所示:
public void run() {
for (int i = 1; i <= 1000; i++) {
synchronized(count){
count.setCount(count.getCount() + 1);
}
}
}
答案 1 :(得分:4)
如果你打破了这一行
count.setCount(count.getCount()+ 1);
分为3个单独的行,它会更清楚:
final int oldCount = count.getCount(); // a
final int newCount = oldCount + 1; // b
count.setCount(newCount); // c
请注意,虽然语句(a)和(c)各自同步,但整个块 不。所以它们仍然可以交错,这意味着线程A可以在线程B执行线程(a)之后进入/执行语句(a)但是之前完成/进入语句(c)。当发生这种情况时,线程(a)和(b)将具有相同的 oldCount ,因此错过了一个增量。
2
join()用于确保在打印之前线程A和线程B都完成。你得到一个较小的数量的原因,因为当你打印你的结果线程A和B可能还没有完成运行。换句话说,即使你完美地同步,没有join(),你仍然会有一个远小于2000的数字。
3。 见2的答案。
答案 2 :(得分:3)
1)因为你没有正确锁定。你正在调用getCount()
,它会锁定,获取值并解锁,递增并调用setCount()
来锁定,保存值并解锁。在某些情况下会发生的情况是,两个线程调用getCount()
,第一个步进锁定,获取值x
并解锁。然后第二个线程获得锁定,获得相同的值x
并解锁。由于两个线程都会递增并稍后保存相同的值,因此您的计数将低于预期值。
2)如果没有join()
,你就不会等待线程完成,你的主线程只会调用getCount()
并在线程仍在运行时获得一个随机值。
3)由于其他线程一直没有锁 (如果你想让它们都运行,它们需要互相给予解锁时间),你需要join()
仍。
答案 3 :(得分:1)
someThread.join()
会导致调用Thread
等到someThread
完成。
如果您移除了join()
来电,那么主Thread
可能会在计数完成之前致电getCount()
,因为someThread
可能仍在运行。
同步方法只是意味着无法在同时对Object
进行多次同步调用。
答案 4 :(得分:1)
一行答案是count.setCount(count.getCount() + 1);
不是原子操作。
或稍微不那么明显,虽然setCount
和getCount
是正确的线程安全和原子的,但没有什么能阻止另一个线程在中的任何一个方法>此帖子致电setCount
和getCount
。这将导致计数迷失。
避免计数丢失的一种方法是创建原子increment()
操作。