Подтвердить что ты не робот

Согласование памяти - происходит до отношения в Java

При чтении документов Java по ошибкам согласованности памяти. Я нахожу точки, связанные с двумя действиями, которые создают событие - до отношения:

  • Когда оператор вызывает Thread.start(), каждое утверждение, которое имеет происходит - до того, как отношения с этим утверждением также будут иметь происходит-до отношения с каждым утверждением, выполняемым новым нить. Эффект кода, который привел к созданию новый поток виден в новом потоке.

  • Когда поток завершается и вызывает Thread.join() в другом потоке для возврата, то все утверждения, выполняемые завершенным поток имеет отношения "доживет" до всех операторов после успешного присоединения. Эффекты кода в потоке теперь видны потоку, который выполнил соединение.

Я не могу понять их смысл. Было бы здорово, если бы кто-нибудь объяснил это простым примером.

4b9b3361

Ответ 1

Современные процессоры не всегда записывают данные в память в том порядке, в котором он был обновлен, например, если вы запускаете псевдокод (предполагая, что переменные всегда сохраняются в памяти здесь для простоты);

a = 1
b = a + 1

... CPU может очень хорошо записать b в память до того, как он напишет a в память. Это не проблема, если вы запускаете вещи в одном потоке, так как поток, выполняющий указанный выше код, никогда не увидит старое значение любой переменной после выполнения назначений.

Многопоточность - это другое дело, вы можете подумать, что следующий код позволит другому потоку поднять значение вашего тяжелого вычисления;

a = heavy_computation()
b = DONE

... другой поток делает...

repeat while b != DONE
    nothing

result = a

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

То же самое было бы - если Thread.start и Thread.join не имели гарантии "произойдет раньше" - вы получите проблемы с кодом, например:

a = 1
Thread.start newthread
...

newthread:
    do_computation(a)

... поскольку a может не иметь значения, сохраненные в памяти при запуске потока.

Поскольку вы почти всегда хотите, чтобы новый поток мог использовать данные, которые вы инициализировали перед его запуском, Thread.start имеет гарантию "произойдет до", то есть данные, которые были обновлены до вызова Thread.start, гарантированы быть доступным для новой нити. То же самое относится к Thread.join, где данные, записанные новым потоком, гарантированно будут видны потоку, который присоединяется к нему после завершения.

Он просто упрощает нарезку резьбы.

Ответ 2

Рассмотрим это:

static int x = 0;

public static void main(String[] args) {
    x = 1;
    Thread t = new Thread() {
        public void run() {
            int y = x;
        };
    };
    t.start();
}

Основной поток изменил поле x. Модель памяти Java не гарантирует, что это изменение будет видимым для других потоков, если они не синхронизированы с основным потоком. Но thread t увидит это изменение, потому что основной поток, называемый t.start() и JLS, гарантирует, что вызов t.start() делает изменение на x видимым в t.run(), поэтому y гарантированно назначается 1.

То же самое касается Thread.join();

Ответ 3

Проблемы с видимостью видимости могут возникать в коде, который неправильно синхронизирован в соответствии с моделью Java-памяти. Из-за оптимизации компилятора и аппаратного обеспечения записи одним потоком не всегда видны при чтении другого потока. Модель памяти Java - это формальная модель, которая делает правила "правильно синхронизированы" ясными, так что программисты могут избежать проблем видимости потоков.

Happens-before - это отношение, определенное в этой модели, и оно относится к конкретным исполнениям. Запись W, которая, как доказано, случается, - до того, как прочитанное R гарантировано будет видно из этого прочитанного, предполагая, что нет никакой другой помехи в записи (т.е. Одна из них не происходит - до отношения с чтением или одно происходит между ними в соответствии с это отношение).

Простейший вид происходит до того, как отношение происходит между действиями в одном потоке. Запись W в V в потоке P выполняется - перед чтением R V в том же потоке, предполагая, что W идет до R в соответствии с порядком программы.

Текст, на который вы ссылаетесь, указывает, что thread.start() и thread.join() также гарантируют, что происходит до отношения. Любое действие, которое происходит - перед thread.start() также происходит до любого действия внутри этого потока. Аналогичным образом, действия внутри потока происходят перед любыми действиями, которые появляются после thread.join().

Какой практический смысл? Если, например, вы запускаете поток и дождитесь его завершения в небезопасной манере (например, сном в течение длительного времени или тестированием некоторого несинхронизированного флага), тогда, когда вы попытаетесь прочитать изменения данных, сделанные поток, вы можете увидеть их частично, таким образом, имея риск несоответствий данных. Метод join() действует как барьер, гарантирующий, что любой фрагмент данных, опубликованных потоком, полностью и последовательно отображается другим потоком.

Ответ 4

В соответствии с документом oracle они определяют, что связь "произойдет раньше" является просто гарантией того, что запись в памяти одним конкретным оператором видна для другого конкретного оператора. p >

package happen.before;

public class HappenBeforeRelationship {


    private static int counter = 0;

    private static void threadPrintMessage(String msg){
        System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) {

        threadPrintMessage("Increase counter: " + ++counter);
        Thread t = new Thread(new CounterRunnable());
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            threadPrintMessage("Counter is interrupted");
        }
        threadPrintMessage("Finish count: " + counter);
    }

    private static class CounterRunnable implements Runnable {

        @Override
        public void run() {
            threadPrintMessage("start count: " + counter);
            counter++;
            threadPrintMessage("stop count: " + counter);
        }

    }
}

Выход будет:

[Thread main] Increase counter: 1
[Thread Thread-0] start count: 1
[Thread Thread-0] stop count: 2
[Thread main] Finish count: 2

Посмотрите вывод, строка [Thread Thread-0] start count: 1 показывает, что все изменения счетчика перед вызовом Thread.start() видны в теге Thread.

И строка [Thread main] Finish count: 2 указывает, что все изменения в теге Thread видны для основного потока, который вызывает Thread.join().

Надеюсь, это поможет вам четко.