Я пробую классическое увеличение / уменьшение переменной 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?
@ValentinMichalak Это просто для моих упражнений. Пробуем все возможные решения, кроме использования AtomicInteger.
Совет к упражнению: никогда не создавайте пустых блоков-ловушек.




Вы вызываете 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 единственным решением этой проблемы?
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();
Почему вы не хотите использовать AtomicInteger?