为什么算得少甚至使用synchronized关键字?

时间:2012-02-27 05:35:42

标签: java concurrency concurrent-programming java.util.concurrent

我开始学习一些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() ?

5 个答案:

答案 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)

  1. 如果你打破了这一行

    count.setCount(count.getCount()+ 1);

  2. 分为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);不是原子操作。

或稍微不那么明显,虽然setCountgetCount是正确的线程安全和原子的,但没有什么能阻止另一个线程在中的任何一个方法>此帖子致电setCountgetCount。这将导致计数迷失。

避免计数丢失的一种方法是创建原子increment()操作。