首页 > 解决方案 > Java SynchronizedCounter 无法按预期工作

问题描述

我有一堂课

class SynchronizedCounter {
    private int i = 0;

    public synchronized void increment() {
        i++;
    }

    public synchronized void decrement() {
        i--;
    }

    public synchronized int getValue() {
        return i;
    }
}

像这样使用:

public class CounterTest {
    public static void main(String[] args) throws InterruptedException {
        SynchronizedCounter c = new SynchronizedCounter();
        Thread d = new Thread(new D(c));
        Thread e = new Thread(new E(c));
        d.start();
        e.start();
        System.out.println(c.getValue());           
    }
}

其中 D 类实现如下:

class D implements Runnable {
    private SynchronizedCounter counter;

    D(SynchronizedCounter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        counter.increment();
    }
}

E类与D类相比只有一条不同的线路

 counter.decrement();

在它的运行方法中。

我希望总是打印 0,因为 SynchronisedCounter 类的方法都是同步的,但是有时我会得到 1。你能解释一下这段代码有什么问题吗?当我在 synchronized(c) 块中运行 d.start() 和 e.start() 时,它按预期工作,当我在 d.start() 和 e.join() 之后添加 d.join() 时也会发生同样的情况e.start()。

标签: javamultithreadingsynchronization

解决方案


您对不正确的线程做出了很多假设。您的两个不成立的主要假设是:

  • 所有线程都以相同的速度运行。
  • 所有线程都立即启动。

执行顺序是随机的。线程同时运行。在下文中,sequence 表示线程SynchronizedCounter通过方法调用命中的顺序。因为这就是这样synchronized做的,它通过用监视器保护并发线程来强制进行顺序访问(如果您不知道监视器是什么,请改为读取互斥信号量,它是不同的,但对于这个解释来说,差异并不重要)。您可以期待以下三个输出中的任何一个:

  • 0如果顺序是 D、E、main;或 E、D、主要;或主要、D、E;或主要,E,D。
  • 1如果序列是 D、main、E
  • -1如果顺序是 E、main、D。

线程不会立即启动

当您调用new Thread().start()时,将Thread变为可运行。但由调度程序决定线程何时真正获得 CPU 时间。这取决于程序影响范围之外的因素,有时甚至是 VM 影响范围之外的因素。例如,任何现有线程由于时间片耗尽而被抢占的可能性有多大,或者空闲 CPU 内核现在是否可用。

线程以不同的速度运行

线程的速度由程序影响范围之外的各种因素决定。例如,如果线程要访问数据,该数据是否被缓存,在哪个缓存中,仅举一个例子。

输出-1是最不可能的输出,但即使是那个输出也不能完全排除。而且“不太可能”也纯粹基于对虚拟机通常行为方式的观察所做出的假设,它不是任何可以依赖的东西。


推荐阅读