Java SynchronizedCounter не работает должным образом - PullRequest
0 голосов
/ 10 ноября 2018

У меня есть класс

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. Не могли бы вы объяснить, пожалуйста, что не так с этим кодом? Когда я запускаю d.start () и e.start () в блоке synchronized (c), он работает как положено, то же самое происходит, когда я добавляю d.join () после d.start () и e.join () после e.start ().

1 Ответ

0 голосов
/ 10 ноября 2018

Вы делаете много предположений о темах, которые не соответствуют действительности. Вот два основных ваших предположения, которые не соответствуют действительности:

  • Все потоки работают с одинаковой скоростью.
  • Все темы запускаются сразу.

Последовательность выполнения является случайной. Потоки работают одновременно. Далее последовательность означает последовательность, в которой потоки нажимают SynchronizedCounter через вызовы метода. Поскольку это то, что делает synchronized, он принудительно запускает параллельные потоки в последовательный доступ, защищая его с помощью монитора (если вы не знаете, что такое монитор, вместо этого читайте семафор мьютекса, это другое, но для этого объяснения разница не важный). Можно ожидать любой из следующих трех выходов:

  • 0 в случае, если последовательность была D, E, main; или E, D, главный; или основной, D, E; или основной, E, D.
  • 1 в случае последовательности D, main, E
  • -1 в случае последовательности E, main, D.

Темы не запускаются сразу

Когда вы вызываете new Thread().start(), Thread становится работоспособным. Но планировщик должен решить, когда поток действительно получает процессорное время. И это зависит от факторов вне сферы влияния программы, иногда даже вне сферы влияния виртуальной машины. Например, насколько близок какой-либо из существующих потоков к вытеснению из-за исчерпания его временного интервала, или из-за того, доступны ли незанятые ядра ЦП прямо сейчас.

Потоки бегут на разных скоростях

Скорость потока определяется различными факторами, которые находятся за пределами влияния программы. Например, если поток хочет получить доступ к данным, независимо от того, кэшируются ли эти данные и в каком кеше он находится, привести только один пример.

Выход -1 является наиболее маловероятным выходом, но даже этот выход нельзя полностью исключить. И это «маловероятное» также основано исключительно на предположениях, сделанных из наблюдений о том, как обычно ведут себя виртуальные машины, и на это нельзя полагаться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...