Java: насколько изменчива гарантия видимости "данных" в этом куске кода?

Class Future
{
    private volatile boolean ready;
    private Object data;
    public Object get()
    {
        if(!ready) return null;
        return data;
    }

    public synchronized void setOnce(Object o)
    {
        if(ready) throw...;
        data = o;
        ready = true;
    }
}

В нем говорилось: "Если поток читает данные, то перед записью возникает крайний случай чтения, гарантирующий видимость данных".

Я знаю из моего обучения:

  1. volatile гарантирует, что каждое чтение / запись будет в памяти, а не только в кеше или регистрах;
  2. volatile обеспечивает переупорядочение: то есть, в методе setOnce() data = o может планироваться только после if(ready) throw... и before ready = true; это гарантирует, что если в get() ready = true, данные должны быть o.

Моя путаница

  1. возможно ли, что когда поток 1 находится в setOnce(), достигает точки, которая после data = o; до готовности = правда; В то же время поток 2 запускает get(), read ready - false и возвращает null. И thead 1 продолжает готов = истина. В этом сценарии поток 2 не видел новые "данные", хотя данным был присвоен новый параметр в потоке 1.

  2. get () не синхронизирован, это означает, что синхронизированная блокировка не может защитить setOnce(), так как поток 1 вызывает get (), которому не нужно получать блокировку для доступа к переменным ready, data. Таким образом, поток не гарантируется, чтобы увидеть последние значения данных. Под этим я подразумеваю, что блокировка гарантирует только видимость между синхронизированными блоками. Даже если один поток выполняет синхронизированный блок setOnce(), другой поток все еще может войти в get () и получить доступ к данным и готовности без блокировки и может увидеть старое значение этих переменных.

  3. в get (), если ready = true, данные должны быть o? Я имею в виду этот поток гарантированно увидеть видимость данных? Я думаю, что данные не изменчивы и не синхронизированы с get (). Неужели этот поток может увидеть старое значение в кеше?

Спасибо!

1 ответ

Решение

volatile гарантирует, что каждое чтение / запись будет в памяти, а не только в кеше или регистрах;

Нету. Это только гарантирует, что это видимо другим потокам. На современном оборудовании это не требует доступа к памяти. (Что хорошо, основная память медленная.)

volatile обеспечивает переупорядочение: то есть, в методе setOnce() data = o может планироваться только после if(ready) throw... и before ready = true; это гарантирует, что если в get() ready = true, данные должны быть o.

Правильно.

возможно ли, что когда поток 1 находится в setOnce(), достигает точки, которая после data = o; до готовности = правда; В то же время поток 2 запускает get(), read ready - false и возвращает null. И thead 1 продолжает готов = истина. В этом сценарии поток 2 не видел новые "данные", хотя данным был присвоен новый параметр в потоке 1.

Да, но если это проблема, вам не следует использовать такой код. Предположительно, API для этого кода будет get гарантированно увидеть результат, если вызвано после setOnce возвращается. Очевидно, вы не можете гарантировать, что get увидим результат, прежде чем мы закончим делать их.

get () не синхронизирован, это означает, что синхронизированная блокировка не может защитить setOnce(), так как поток 1 вызывает get (), которому не нужно получать блокировку для доступа к переменным ready, data. Таким образом, поток не гарантируется, чтобы увидеть последние значения данных. Под этим я подразумеваю, что блокировка гарантирует только видимость между синхронизированными блоками. Даже если один поток выполняет синхронизированный блок setOnce(), другой поток все еще может войти в get () и получить доступ к данным и готовности без блокировки и может увидеть старое значение этих переменных.

Нет. И если бы это было правдой, синхронизацию было бы почти невозможно использовать. Например, общий шаблон - создать объект, затем получить блокировку для коллекции и добавить объект в коллекцию. Это не сработает, если получение блокировки коллекции не гарантирует, что записи, вовлеченные в создание объекта, будут видимы.

в get (), если ready = true, данные должны быть o? Я имею в виду этот поток гарантированно увидеть видимость данных? Я думаю, что данные не изменчивы и не синхронизированы с get (). Неужели этот поток может увидеть старое значение в кеше?

в Java volatile Операция определена так, что поток, который видит изменение в одном, гарантированно увидит, что вся другая память изменяет поток, который сделал это изменение, сделанное до того, как он сделал изменение, увиденное потоком. Это не так в других языках (таких как C или C++). Это может сделать летучие компоненты Java более дорогими на некоторых платформах, но, к счастью, не на обычных платформах.

Также, пожалуйста, не говорите о "в кеше". Это не имеет ничего общего с кэшем. Это распространенное недоразумение. Это связано с видимостью, а не с кэшированием. Большинство кешей обеспечивают полную видимость в кеше (добавьте "протокол MESI" в свою любимую поисковую систему, чтобы узнать больше) и не требуют ничего особенного для обеспечения видимости.

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