【问题标题】:Why count less even used synchronized keyword?为什么少用 synchronized 关键字?
【发布时间】:2012-02-27 05:35:41
【问题描述】:

我开始学习一些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() 有什么意义呢?

【问题讨论】:

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


    【解决方案1】:

    这很简单。您通过调用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); 
          }
        } 
    } 
    

    【讨论】:

    • PS:连接在那里,所以你的主线程在打印之前等待其他线程完成。
    【解决方案2】:
    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) 之后进入/执行语句 (a),但 它完成/进入语句 (c) 之前。发生这种情况时,线程 (a) 和 (b) 将具有相同的 oldCount,因此会错过一个增量。

    2.

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

    3。 请参阅 2 的答案。

    【讨论】:

      【解决方案3】:

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

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

      3) 由于其他线程并非一直持有锁(如果你想让它们都运行,它们毕竟需要给彼此解锁时间),你需要join()still。

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

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

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

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

          【讨论】: