Java - поток и статистика c переменная - PullRequest
1 голос
/ 05 августа 2020

Только что начал с потоков в java, и я не могу рассуждать с выводом моей программы

public class ThreadExample extends Thread{
    private int info;
    static int x = 0;

    public ThreadExample (int info) {
        this.info = info;
    }

    public void run () {
        if ( info == 1 )    {
            x = 3;
            System.out.println(Thread.currentThread().getName() + " " + x);
        } else{
            x = 1;
            System.out.println(Thread.currentThread().getName() + " " + x);
        }
    }

    public static void main (String args []) {
        ThreadExample aT1  = new ThreadExample(1);
        ThreadExample aT2  = new ThreadExample(2);
        aT1.start();
        aT2.start();
        System.err.println(x);
    }
}

Вывод:

Thread-0 3
Thread-1 1
3

Почему он печатает 3 даже если второй поток изменил значение переменной stati c на 1?

Будет ли одновременно выполняться 3 потока?

Ответы [ 6 ]

5 голосов
/ 05 августа 2020

Если вы изменяете переменную в одном потоке, она не сразу (или обязательно когда-либо ) становится видимой для 2-го потока, если вы не используете какой-либо примитив синхронизации, такой как Mutex. Вы также можете использовать классы atomi c, такие как AtomicInteger, чтобы гарантировать, что изменения, сделанные в одном потоке, станут видимыми для другого.

В документации . * Доступно гораздо больше информации .

3 голосов
/ 05 августа 2020

Два возможных сценария ios

  1. Поток 2 обновил бы x до Потока 1. Вы не можете определить, как выполнение чередовалось между двумя потоками на основе порядка выполнения операторы печати, которые вы видите.

  2. Потоки действительно выполняются в ожидаемом вами порядке. Но поскольку x не volatile, вы можете не увидеть обновленное значение.

См. - Какое ключевое слово volatile полезно для

2 голосов
/ 05 августа 2020

Вы не можете предсказать результат многопоточности.

Он может отличаться, если вы запускаете свой код на другом устройстве или несколько раз.

Вы не можете (или не должны) полагаться на время или планировщик.

Я думаю, что параллелизм / энергонезависимость сама по себе может быть не единственной проблемой, но вы также можете принять во внимание промывку:

x=3 (ThreadExample(1))
sysout 3 (ThreadExample(1))
syserr x (main thread)
x=1 (ThreadExample(2))
sysout 3 (ThreadExample (2))
flush stdout (caused by jvm exit)
flush stderr (caused by jvm exit)

Обратите внимание на грипп sh в конце. stdout и stderr не могут быть синхронизированы.

Эти потоки буферизуются и записываются в консоль в любое время.

Хотя две вещи, записанные в stdout или stderr, гарантированно будут записаны в правильном порядке , это не гарантировано, если вы отправляете одно в стандартный вывод, а другое в стандартный поток.

Также гарантируется, что все, что напечатано в stdout и stderr, записывается, когда jvm завершается нормально (нет kill -9 или аналогичный).

Если jvm пишет stdout перед stderr, вы можете получить свой результат.

Если вы хотите, чтобы выходные данные печатались правильно, вы можете сделать две вещи:

  • Вызвать flush вручную после печати

  • создайте блок synchronized (или аналогичный) вокруг операции, println и flush. (Обратите внимание, что при этом вы можете немного потерять производительность / параллелизм)

Если вы хотите проверить, имеет ли промывка значение в вашем случае, добавьте System.err.flush(); (чтобы stderr сбрасывается перед stdout) в конце вашей профраммы и посмотрите, есть ли разница.

Кроме того, есть еще кое-что, чего я не нашел в других ответах, а именно: оптимизация JIT.

Компилятор JIT может оптимизировать вашу программу. Например, он может оптимизировать:

x=3;
System.out.println(x);

до:

x=3;
System.out.println(3);

, чтобы печатать 3, даже если это не 3 в то время, когда println называется.

1 голос
/ 05 августа 2020

Будет ли одновременно работать 3 потока?

Да. Первый поток - это основной поток, тот, который все это запустил, тот, который вызвал ваш метод public static void main (String args []). Весь код выполняется в потоке. Затем ваш основной метод запускает 2 потока. Поскольку вы начали с 1, теперь у вас есть 3.

На вопрос, почему окончательный вывод основного потока - 3, трудно ответить, потому что у вас есть состояние гонки. У вас есть 3 потока, читающих переменную, в то время как 2 из них обновляются, и все это происходит одновременно.

x = 3;
System.out.println(Thread.currentThread().getName() + " " + x);

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

Также рассмотрите ключевое слово volatile. Без него JVM может кэшировать в потоке копии общих значений, что может привести к устареванию при чтении и записи между потоками. Какое ключевое слово volatile полезно для

1 голос
/ 05 августа 2020

Переменные не рекомендуются для обмена информацией между потоками. Используйте BlockingQueues для сообщений, Semaphores и CountDownLatches для сигналов. Короче говоря, передача значения должна не только выполнять незаметное присвоение, но и создавать какое-то событие для уведомления других потоков. Мне нравится слово «жетон» для таких объектов.

0 голосов
/ 05 августа 2020

Результат потоков непредсказуем.

Для обеспечения согласованного / предсказуемого поведения используйте значения volatile / Atomi c, чтобы изменения были видны другим потокам

...