在以下示例中声明同步方法的目的是什么?

时间:2014-12-19 05:10:39

标签: java multithreading

您可以复制粘贴以下示例以在本地计算机上运行。我正在学习MultiThreading,我在网上看到了这个例子。我很困惑,为什么方法添加(内部类计数器)会产生差异,如果声明同步,你可以删除从它同步的关键字,它不会影响最终结果所以我只是想知道什么是目的通过声明同步来提供服务

class Counter {

    long count = 0;

    public synchronized void add(long value) {
        this.count += value;
        System.out.println(count + "-" + Thread.currentThread().getName());
    }
}

class CounterThread extends Thread {

    protected Counter counter = null;

    public CounterThread(Counter counter) {
        this.counter = counter;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                Logger.getLogger(CounterThread.class.getName()).log(Level.SEVERE, null, ex);
            }
            counter.add(i);
        }
    }
}

public class Example {

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread threadA = new CounterThread(counter);
        Thread threadB = new CounterThread(counter);
        Thread threadC = new CounterThread(counter);

        threadA.start();
        threadB.start();
        threadC.start();
    }
}

3 个答案:

答案 0 :(得分:4)

将类声明为synchronized意味着无论什么线程想要执行该方法,都必须首先获取当前对象的锁。该对象只有一个锁,只有获得它的线程才能执行该方法。其他线程必须等到锁定保持线程完成。完成该方法后,执行线程将释放锁。

简而言之。 synchronized关键字确保在任何给定时间只有一个线程可以执行该方法。

同步对于(a)系统是多线程的类很重要; (b)该类具有可在该方法中更改的实例变量。当两个或多个线程同时执行相同的方法时,通常会发生多线程问题。一个线程可以根据其输入更改实例变量,而另一个线程可以看到这些更改。

在此特定示例中,实例变量count可能会出现问题。如果一个线程将其设置为5,则第二个线程可以出现并将其设置为10.当第一个线程然后执行System.out.println调用时,它会输出值10 - 这是意外行为。

答案 1 :(得分:4)

synchronized关键字阻止threadA,threadB和threadC同时执行操作。了解this.count += value在内部实现为三个操作非常重要:

  1. 读取计数字段
  2. 增值
  3. 写入计数字段
  4. 如果删除synchronized关键字,则可以想象以下事件序列:

    1. threadA读取计数字段(0)
    2. threadB读取count字段(0)
    3. threadA将值添加到count(例如,#1 +值= 1的count = 0)
    4. threadB将值添加到count(例如,#2 +值= 1的count = 0)
    5. threadA写入计数字段(#3中的1)
    6. threadB写入计数字段(#4中的1(!),因此#5中的写入操作丢失)

答案 2 :(得分:3)

使add()方法同步很重要的原因是这一行:

this.count += value;

不是原子动作。它真的编译成这个(或类似的):

long c = count;
c = c + value;
count = c;

显然线程安全。