Поток обновления ссылки безопасен?

public class Test{
   private MyObj myobj = new MyObj(); //it is not volatile


   public class Updater extends Thred{
      myobje = getNewObjFromDb() ; //not am setting new object
   }

   public MyObj getData(){
    //getting stale date is fine for 
    return myobj;
   }


}

Обновляется регулярно обновляет myobj
Другие классы получают данные, используя getData
Безопасен ли этот поток кода без использования ключевого слова volatile?
Я думаю да. Может кто-нибудь подтвердить?

6 ответов

Нет, это не потокобезопасно. (Что заставляет вас думать, что это?)

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

Короче говоря, это в основном означает, что чтение и запись synchronized (на том же мониторе) или делая ссылку volatile,

Без этого нет никаких гарантий, что читающий поток увидит обновление - и он даже не будет таким простым, как "ну, он либо увидит старое значение, либо новое значение". Ваши читательские потоки могут увидеть очень странное поведение с повреждением данных, которое может последовать. Посмотрите, например, как отсутствие синхронизации может привести к бесконечным циклам (комментарии к этой статье, особенно Брайана Гетца, стоит прочитать):

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

Нет, это не так.

Без volatileзвонит getData() из другого потока может вернуть устаревшее кэшированное значение.
volatile принудительно отображает назначения из одного потока во всех других потоках.

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

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

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

Если вы объявите ссылку как volatileВы создаете точку синхронизации вокруг переменной. Проще говоря, это означает, что весь кэш доступа к потоку очищен (записи записываются, а чтения забываются).

Единственные типы, которые не получают атомарного чтения / записи, long а также double потому что они больше, чем 32-разрядные на 32-разрядных компьютерах.

Если MyObj является неизменным (все поля являются окончательными), вам не нужно volatile.

Volatile будет работать для логических переменных, но не для ссылок. Myobj, похоже, работает как кешированный объект, он может работать с AtomicReference. Поскольку ваш код извлекает значение из БД, я оставлю код как есть и добавлю к нему AtomicReference.

import java.util.concurrent.atomic.AtomicReference;

    public class AtomicReferenceTest {
        private AtomicReference<MyObj> myobj = new AtomicReference<MyObj>();

        public class Updater extends Thread {

            public void run() {
                MyObj newMyobj = getNewObjFromDb();
                updateMyObj(newMyobj);
            }

            public void updateMyObj(MyObj newMyobj) {
                myobj.compareAndSet(myobj.get(), newMyobj);
            }

             }
        public MyObj getData() {
             return myobj.get();
        }
    }

    class MyObj {
    }

Большая проблема с этим видом кода - ленивая инициализация. Без volatile или же synchronized ключевые слова, вы можете назначить новое значение myobj это не было полностью инициализировано. Модель памяти Java допускает выполнение части конструкции объекта после возврата конструктора объекта. Это переупорядочение операций с памятью является причиной того, что барьер памяти так важен в многопоточных ситуациях.

Без ограничения по объему памяти гарантия невозможна, поэтому вы не знаете, MyObj был полностью построен. Это означает, что другой поток может использовать частично инициализированный объект с неожиданными результатами.

Вот еще несколько подробностей о синхронизации конструктора:

Конструктор синхронизации в Java

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