Увеличение и уменьшение в многопоточной среде

Я пробую классическое увеличение / уменьшение переменной int в многопоточной среде. Это мой пример кода.

public class SyncIncDec {


    public static void main(String[] args) {

        SyncCounter count = new SyncCounter();

        Thread incThread = new Thread(() -> {
            count.increment();
        });

        Thread decThread = new Thread(() -> {
            count.decrement();
        });

        Thread displayThread = new Thread(() -> {
            System.out.println("Count value : " + count.getX());
        });

        incThread.start();
        decThread.start();
        displayThread.start();      

        try {
            incThread.join();
        } catch (InterruptedException e) {
//          e.printStackTrace();
        }

        try {
            decThread.join();
        } catch (InterruptedException e) {
//          e.printStackTrace();
        }

        try {
            displayThread.join();
        } catch (InterruptedException e) {
//          e.printStackTrace();
        }

    }

}


class SyncCounter {

    private int x=0;

    public SyncCounter() {
        super();
    }

    public SyncCounter(int y) {
        super();
        x = y ;
    }

    synchronized int  getX() {
        return x; 
    }

    void setX(int y) {
        x = y ;
    }

    void increment() {
        ++x;
    }


    void decrement() {
        --x;
    }

}

Хотя я использовал метод join () для всех трех потоков, я все равно получаю противоречивые результаты. Разве здесь не соединение означает, что основной поток будет ждать, пока каждый из потоков не завершит свое выполнение? Я даже пробовал добавить синхронизацию к каждой из трех сигнатур методов; но я получаю противоречивые результаты.

Помимо использования атомарной версии переменной, как еще я могу гарантировать, что всегда получаю 0?

Почему вы не хотите использовать AtomicInteger?

Valentin Michalak 11.06.2018 13:13

@ValentinMichalak Это просто для моих упражнений. Пробуем все возможные решения, кроме использования AtomicInteger.

WonderWoman 11.06.2018 13:21

Совет к упражнению: никогда не создавайте пустых блоков-ловушек.

GhostCat 11.06.2018 13:30
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
881
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы вызываете join() в трех потоках только после того, как все потоки были запущены. Таким образом, у вас нет гарантии, что поток, на который ссылается переменная displayThread, будет запущен после потоков, которые увеличивают и уменьшают счетчик. Чтобы убедиться в этом, вызовите join() в этих потоках после их запуска:

incThread.start();
decThread.start();
incThread.join();
decThread.join();
displayThread.start(); 

Он будет блокировать текущий поток до тех пор, пока не будет выполнено увеличение и уменьшение, и какой бы порядок ни был вызван join() после вызова start() этих потоков.

Ответ принят как подходящий

Ваш SyncCounter вообще не является потокобезопасным. Увеличение и уменьшение изменяемых методов должно быть синхронизировано. В наши дни правильный способ реализовать такой класс был бы в атомные речи. Например:

class SyncCounter {

    private final AtomicInteger x;

    public SyncCounter() {
     this(0);   
    }

    public SyncCounter(int x) {
       this.x = new AtomicInteger(x);
    }

    int getX() {
        return x.get(); 
    }

    void setX(int x) {
        this.x.set(x);
    }

    int increment() {
        return x.incrementAndGet();
    }


    int decrement() {
        return x.decrementAndGet();
    }

}

И тестовый код:

    final Thread incThread = new Thread(() -> {
        count.increment();
    });

    final Thread decThread = new Thread(() -> {
        count.decrement();
    });

    Thread displayThread = new Thread(() -> {
        incThread.join();
        decThread.join();
        System.out.println("Count value : " + count.getX());
    });

Я добавил ключевое слово synchronized во все три сигнатуры методов; но я получил противоречивые результаты. Является ли использование AtomicInteger единственным решением этой проблемы?

WonderWoman 11.06.2018 13:21

Though I have used join() method for all the three threads, I still get inconsistent results. Doesn't join here mean for the main thread to wait until each of the thread has completed its execution?

У вас есть 2 проблемы в вашем коде.

  • В вашем классе SyncCounter синхронизируется только метод getX(). Поскольку у вас есть 3 потока, совместно использующих один и тот же экземпляр этого класса, любой метод, который читает или обновляет общие поля, должен быть synchronized. Это означает, что метод increment() и decrement() также должен быть synchronized. Как упоминал @Victor, замена SyncCounter на AtomicInteger - простое решение, хотя я подозреваю, что ваше упражнение нужно делать вручную.

    ...
    synchronized int increment() {
    ...
    synchronized int decrement() {
    
  • В вашей модели потока у вас есть состояние гонки между потоками увеличения и уменьшения и потоком отображения. Тот факт, что вы запускаете поток отображения после других, не означает, что он выполняется последним. Может случиться так, что поток отображения завершится первым, и в этом случае он напечатает 0 или может напечатать -1 или 1 в зависимости от условий гонки. Самым простым решением здесь является соединение основного потока с потоками увеличения и уменьшения, а тогда распечатывает результат.

    // start the inc and dec threads running in the background
    incThread.start();
    decThread.start();
    // wait for inc thread to finish
    incThread.join();
    // wait for dec thread to finish
    decThread.join();
    // now we can print out the value of the counter
    System.out.println("Count value : " + count.getX());
    

    Если у вас должен быть поток отображения, он должен быть запущен после соединения потоков увеличения и уменьшения, такие как @davidxxx рекомендует.

    // start the inc and dec threads running in the background
    incThread.start();
    decThread.start();
    // wait for inc thread to finish
    incThread.join();
    // wait for dec thread to finish
    decThread.join();
    // now start the display thread now that the increment/decrement is done
    displayThread.start();
    // wait for the display thread to finish
    displayThread.join();
    

Другие вопросы по теме