Почему volatile не работает должным образом

Сегодня я создавал одну тайм-аут работу, используя TimerTask но попал в новую проблему, где у меня есть static volatile boolean переменная flag, Насколько я понимаю, как только значение этой переменной изменяется, она уведомляется всеми работающими thread, Но когда я запустил эту программу, я получил вывод ниже, что не приемлемо.

O / P:

--------------
--------------
DD
BB
Exiting process..
CC

Я ожидаю, что моим последним отпечатком должен быть процесс выхода. Почему это странное поведение?

Мой код:

public class TimeOutSort {

    static volatile boolean flag = false;

    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {

                flag = true;
                System.out.println("Exiting process..");
                // System.exit(0);
            }
        }, 10 * 200);

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag)
                    System.out.println("BB");

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag)
                    System.out.println("CC");

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag)
                    System.out.println("DD");

            }
        }).start();
    }

}

Редактировать: Как я могу достичь этого?

6 ответов

Решение

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

Это не заставляет поток CC-печати фактически запускаться сразу после flag был установлен на true, Вполне возможно (особенно на одноядерном компьютере), что один поток устанавливает флаг и печатает сообщение еще до того, как поток CC-печати сможет запустить.

Также: обратите внимание, что печать на System.out включает в себя приобретение блокировки (где-то внутри println() вызов), который может изменить многопоточное поведение тестового кода.

Потоки могут выполнять код в любом порядке

thread BB: while (!flag) // as flag is false
thread Main:   flag = true;
thread Main:   System.out.println("Exiting process..");
thread BB:     System.out.println("BB");

Я ожидаю, что моя последняя распечатка должна быть завершена.

Потоки предназначены для одновременной и независимой работы. Было бы удивительно, если бы это всегда было последним утверждением, потому что вы не можете быть уверены, где находится каждый поток, когда вы устанавливаете флаг.

Поток, который печатает "CC", не получал никакого времени ЦП, пока ваш поток, который печатает "Exiting process...", не напечатал это. Это ожидаемое поведение.

Это не изменчиво не работает (если бы не было, некоторые из ваших потоков не остановились бы). Речь идет о порядке выполнения инструкций в разных потоках, и это случайный (зависит от планирования ОС), если вы явно не синхронизируете циклы на промежуточных шагах.

Чтобы добавить альтернативное выражение к полученным вами объяснениям: в выходных данных вашего примера, поток, который печатает "CC" был подвешен (где-то) "между" линиями while (!flag) а также System.out.println(), Это означает, что после того, как он проснется, println() выполняется до следующей проверки флага. (Он также не проснется только потому, что вы изменили значение флага, а потому, что некоторые другие потоки блокируют или используют его временной интервал.)

Я не проверял, но вы можете достичь этого, как это

public class TimeOutSort {

static volatile boolean flag = false;

public static void main(String[] args) {

    Timer timer = new Timer();
    timer.schedule(new TimerTask() {

        @Override
        public void run() {

            synchronized(flag){
                 flag = true;
                 notifyAll();
            }


        }
    }, 10 * 200);

    new Thread(new Runnable() {

        @Override
        public void run() {

            synchronized(flag){
                if(!flag)
                {
                    wait();
                }
                System.out.println("BB");

            }


        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {

              synchronized(flag){
                if(!flag)
                {
                    wait();
                }
                System.out.println("CC");

            }
        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {

              synchronized(flag){
                if(!flag)
                {
                    wait();
                }
                System.out.println("DD");

            }


        }
    }).start();
}

}

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