Почему потоки не работают должным образом?

#java #multithreading

#java #многопоточность

Вопрос:

Вот мой следующий код:

 class Test {
private int x  = 0;

public void incX() {
    synchronized(this) {
         x;                        
    }
    System.out.println("x is: " x "     " Thread.currentThread().getName());
}

public static void main(String[] args) {
    Test test = new Test();             
    Thread t1 = new Thread(() -> {
        test.incX();                    
    });
    Thread t2 = new Thread(() -> {
        test.incX();                   
    });
    t1.start();
    t2.start();

    System.out.println("Done");
}
}
  

Вот мой вывод:

 x is: 2     Thread-1
x is: 1     Thread-0
  

Здесь поток t2 выводит 2, но тогда поток t1 также должен выводить 2, верно? Поскольку, когда x равно 2, оно должно быть видно потоку t1 , верно? Итак, почему поток t1 равен 1?

Как это возможно, что поток t2 выводит 2, а затем поток t1 выводит 1? Поскольку поток t2 уже выводит 2, тогда значение x должно быть 2. Итак, как поток t1 все еще может выводить 1? Я неправильно понимаю?

Комментарии:

1. Вы, вероятно, неправильно понимаете.

2. @duffymo: Можете ли вы объяснить мне, что я неправильно понимаю?

3. Другие проделали лучшую работу. Мне нечего добавить, кроме как сказать, что когда код не соответствует вашим ожиданиям, лучше проверить ваши предположения.

Ответ №1:

Начнем с того, что ваш код неправильно синхронизирован. Ваше выражение

 "x is: " x "     " Thread.currentThread().getName()
  

считывает значение общей переменной x без синхронизации, то есть в гонке данных. Однако он, по крайней мере, увидит значение, указанное в предыдущем synchronized блоке.

Теперь давайте рассмотрим возможные результаты вашей программы. Потоки выполняются шаг за шагом, и шаги всех потоков чередуются. Рассмотрим следующую последовательность:

  1. Thread-0 вводит synchronized блок, считывает x == 0 , обновляет его 1 и покидает блок. Последующее строковое выражение считывает это значение 1 .
  2. Thread-1 вводит synchronized блок, считывает x == 1 , обновляет его 2 и покидает блок. Последующее строковое выражение считывает это значение 2 .
  3. Thread-1 вводит synchronized метод println и выводит его результат 2 .
  4. Thread-0 вводит synchronized метод println и выводит его результат 1.

Ответ №2:

Вся ваша System.out.println строка очень далека от атомной.
Например, между построением строки и вызовом System.out.println может произойти много чего.

Давайте рассмотрим эквивалентный блок кода:

 public void incX() {
    synchronized(this) {
         x;                        
    }
    String implicit = "x is: "   x   "     "   Thread.currentThread().getName();
    // <-- "Point X"
    System.out.println(implicit);
}
  

Теперь сценарий может выглядеть следующим образом:

  • Поток 1 запускается и продолжается до тех пор, пока не достигнет «точки X», создав строку, которая гласит:

    x равно: 1 поток-0

  • Поток 2 запускается и продолжается до тех пор, пока не достигнет «точки X», создав строку, которая гласит:

    x равно: 2 потока-1

  • Поток 2 вызывает System.out.println , делая общий результат до сих пор:

    x равно: 2 потока-1

  • Поток 1 вызывает System.out.println , делая общий вывод:

    x — это: 2 потока-1 x — это: 1 поток-0

Копирование значения x в другую переменную внутри synchronized блока должно быть достаточным, чтобы поведение соответствовало вашим ожиданиям:

 public void incX() {
    int val;
    synchronized(this) {
       val =   x;                        
    }
    System.out.println("x is: "   val   "     "   Thread.currentThread().getName());
}