3

我开始学习一些 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()); 
    } 
} 
问题:
1. 多次结果都小于2000,为什么?
2.如果删除2条join()语句,为什么count.getCount = 451,甚至更少?
3.我认为删除join()语句不会有任何影响,
因为我已经有同步方法每次将一个对象锁定到一个线程?
那么,使用 Synchronized 和 join() 有什么意义呢?
4

5 回答 5

4

这很简单。您通过调用getCount + 1来调用setCount方法。在进入方法之前,运行时评估getCount(同步),但是离开getCount并进入setCount时您不持有锁,其他线程可以进入调用getCount。因此,时不时地有两个(或更多,取决于您创建的线程数)线程在 getCount 中具有相同的值。假设线程 A 进入并在 getCount 中接收到值 1。运行时执行到调用 getCount 并接收相同值 1 的执行 B。线程 B 将值设置为 1 并再运行 50 次,因此在该阶段您的计数将为 50。运行时将执行交给线程 A,该线程使用 1 调用 setCount(请记住,它没有设法调用 setCount 并产生了它的 exec)。现在 A 将值设置为 1(这是错误的)。

更改您运行的实现,如下所示:

public void run() { 
    for (int i = 1; i <= 1000; i++) {
      synchronized(count){ 
        count.setCount(count.getCount() + 1); 
      }
    } 
} 
于 2012-02-27T05:51:24.027 回答
4
  1. 如果你断线

    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) 之后但它完成/进入语句 (c) 之前进入/执行语句 (a)。发生这种情况时,线程 (a) 和 (b) 将具有相同的oldCount,因此会错过一个增量。

2.

join() 是为了确保线程 A 和线程 B 在打印之前都完成。如果您打印结果线程 A 和 B 可能还没有完成运行,那么您获得较小计数的原因。换句话说,即使你完美地同步,没有 join(),你仍然会得到一个远小于 2000 的数字。

3. 见 2 的答案。

于 2012-02-27T06:07:09.293 回答
3

1)因为你没有正确锁定。您正在调用getCount(),它锁定,获取值并解锁,递增并调用setCount()哪些锁定,保存值并解锁。在某些情况下发生的情况是两个线程都调用getCount(),第一个线程锁定,获取值x并解锁。然后第二个线程获得锁,获得相同的值x并解锁。由于两个线程都会递增并稍后保存相同的值,因此您将获得比预期更低的计数。

2)没有join()你不会等待你的线程完成,你的主线程只会getCount()在线程仍在运行时调用并获得一个随机值。

3)由于其他线程并非一直持有锁(如果您希望它们都运行,它们毕竟需要给彼此解锁时间),您将需要join()静止。

于 2012-02-27T05:52:00.907 回答
1

someThread.join()将导致调用Thread等待直到someThread完成。

如果您删除join()调用,则 mainThread可能会getCount()在计数完成之前调用,因为someThread可能仍在运行。

Object同步方法只是意味着不能同时an 进行多个同步调用。

于 2012-02-27T05:47:34.990 回答
1

一条线的答案是这count.setCount(count.getCount() + 1);不是原子操作。

或者稍微不那么神秘,虽然setCount并且getCount是适当的线程安全和原子的,但没有什么可以阻止另一个线程在该线程调用and之间调用这些方法中的任何一个。这将导致计数丢失。setCountgetCount

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

于 2012-02-27T06:11:14.093 回答