Увеличение и уменьшение в многопоточной среде - PullRequest
0 голосов
/ 11 июня 2018

Я пробую классический приращение / уменьшение переменной 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 всегда?

Ответы [ 3 ]

0 голосов
/ 11 июня 2018

Ваш 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());
    });
0 голосов
/ 19 июня 2018

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

У вас есть 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();
    
0 голосов
/ 11 июня 2018

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

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

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

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