Синхронизация не конечного поля

Предупреждение отображается каждый раз, когда я синхронизируюсь в поле не конечного classа. Вот код:

public class X { private Object o; public void setO(Object o) { this.o = o; } public void x() { synchronized (o) // synchronization on a non-final field { } } } 

поэтому я изменил кодировку следующим образом.

  public class X { private final Object o; public X() { o = new Object(); } public void x() { synchronized (o) { } } } 

Я не уверен, что приведенный выше код – это правильный способ синхронизации в поле не конечного classа. Как синхронизировать не окончательное поле?

    Прежде всего, я рекомендую вам реально попытаться решить проблемы параллелизма на более высоком уровне абстракции, то есть решить его, используя classы из java.util.concurrent, такие как ExecutorServices, Callables, Futures и т. Д.

    Тем не менее, нет ничего плохого в синхронизации в не конечном поле как таковое. Вам просто нужно иметь в виду, что если ссылка на объект изменяется, то один и тот же раздел кода может выполняться параллельно . То есть, если один stream выполняет код в синхронизированном блоке, а кто-то вызывает setO(...) , другой stream может одновременно запускать один и тот же синхронизированный блок на одном экземпляре .

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

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

    Предполагая, что синхронизированные блоки предназначены для обеспечения того, чтобы только один stream обращался к некоторым общим данным за раз, подумайте:

    • Поток 1 входит в синхронизированный блок. Yay – он имеет эксклюзивный доступ к общим данным …
    • Тема 2 вызова setO ()
    • Поток 3 (или еще 2 …) входит в синхронизированный блок. Ик! Он считает, что он имеет эксклюзивный доступ к общим данным, но stream 1 все еще дергается с ним …

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

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

    Правило №1: Если поле не является окончательным, всегда используйте фиктивный финал фишек (закрытый).

    Причина № 1: Вы удерживаете блокировку и самостоятельно изменяете ссылку переменной. Другой stream, ожидающий вне синхронизированной блокировки, сможет войти в защищенный блок.

    Причина № 2: вы удерживаете блокировку, а другой stream меняет ссылку переменной. Результат тот же: другой stream может войти в защищенный блок.

    Но при использовании фиктивного фиктивного замка возникает другая проблема : вы можете получить неправильные данные, потому что ваш не конечный объект будет синхронизироваться только с ОЗУ при вызове synchronize (object). Итак, как второе правило:

    Правило № 2: при блокировке не конечного объекта вам всегда нужно выполнять оба действия: использование фиктивной фиктивной блокировки и блокировки не конечного объекта ради синхронизации ОЗУ. (Единственная альтернатива будет объявлять все поля объекта как изменчивые!)

    Эти замки также называются «вложенными замками». Обратите внимание, что вы должны вызывать их всегда в одном порядке, иначе вы получите мертвую блокировку :

     public class X { private final LOCK; private Object o; public void setO(Object o){ this.o = o; } public void x() { synchronized (LOCK) { synchronized(o){ //do something with o... } } } } 

    Как вы можете видеть, я пишу два замка прямо на одной строке, потому что они всегда принадлежат друг другу. Например, вы можете сделать 10 блокировок вложенности:

     synchronized (LOCK1) { synchronized (LOCK2) { synchronized (LOCK3) { synchronized (LOCK4) { //entering the locked space } } } } 

    Обратите внимание, что этот код не будет прерываться, если вы просто приобретете внутреннюю блокировку, подобную synchronized (LOCK3) помощью других streamов. Но он сломается, если вы вызовете в другом streamе что-то вроде этого:

     synchronized (LOCK4) { synchronized (LOCK1) { //dead lock! synchronized (LOCK3) { synchronized (LOCK2) { //will never enter here... } } } } 

    Во время обработки незавершенных полей существует только один способ обхода таких вложенных замков:

    Правило №2 – Альтернатива: объявлять все поля объекта как изменчивые. (Я не буду говорить здесь об ошибках этого, например, предотвращать любое хранение в кэшах x-уровня даже для чтения, aso.)

    Итак, aioobe совершенно прав: просто используйте java.util.concurrent. Или начните понимать все о синхронизации и сделать это самостоятельно с помощью вложенных блокировок. 😉

    Для получения дополнительной информации о том, почему синхронизация по незавершенным полям ломается, просмотрите мой тестовый пример: https://stackoverflow.com/a/21460055/2012947

    И для более подробной информации, почему вам требуется синхронизация вообще из-за RAM и кэшей, смотрите здесь: https://stackoverflow.com/a/21409975/2012947

    Если o никогда не изменяется на время жизни экземпляра X , вторая версия является лучшим стилем независимо от того, задействована ли синхронизация.

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

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

    Я думаю, именно поэтому это просто предупреждение: вы, вероятно, делаете что-то неправильно, но это может быть и правильно.

    Я на самом деле не вижу здесь правильного ответа, то есть, это нормально.

    Я даже не знаю, почему это предупреждение, в этом нет ничего плохого. JVM гарантирует, что вы получите верный объект назад (или null), когда вы прочитаете значение, и вы можете синхронизировать любой объект.

    Если вы планируете фактически изменить блокировку во время ее использования (вместо того, чтобы, например, изменить ее из метода init, перед тем, как начать ее использовать), вы должны сделать переменную, которую вы планируете изменить, volatile . Затем все, что вам нужно сделать, это синхронизировать как с старым, так и с новым объектом, и вы можете безопасно изменить значение

     public volatile Object lock; 

     synchronized (lock) { synchronized (newObject) { lock = newObject; } } 

    Там. Это не сложно, написание кода с помощью блокировок (мьютексов) очень просто. Написание кода без них (незаблокированный код) – это то, что сложно.

    EDIT: Таким образом, это решение (как предложил Джон Скит) может иметь проблему с атомарностью реализации «synchronized (object) {}», в то время как ссылка на объект меняется. Я спросил отдельно, и, по словам г-на Эриксона, он не является streamобезопасным – см .: Входит ли синхронный блок атомный? , Так что возьмите это как пример, как НЕ делать это – со ссылками почему;)

    См. Код, как он будет работать, если synchronized () будет атомарным:

     public class Main { static class Config{ char a='0'; char b='0'; public void log(){ synchronized(this){ System.out.println(""+a+","+b); } } } static Config cfg = new Config(); static class Doer extends Thread { char id; Doer(char id) { this.id = id; } public void mySleep(long ms){ try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();} } public void run() { System.out.println("Doer "+id+" beg"); if(id == 'X'){ synchronized (cfg){ cfg.a=id; mySleep(1000); // do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend // here it would be modifying different cfg (cos Y will change it). // Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object cfg.b=id; } } if(id == 'Y'){ mySleep(333); synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok { cfg = new Config(); // introduce new configuration // be aware - don't expect here to be synchronized on new cfg! // Z might already get a lock } } if(id == 'Z'){ mySleep(666); synchronized (cfg){ cfg.a=id; mySleep(100); cfg.b=id; } } System.out.println("Doer "+id+" end"); cfg.log(); } } public static void main(String[] args) throws InterruptedException { Doer X = new Doer('X'); Doer Y = new Doer('Y'); Doer Z = new Doer('Z'); X.start(); Y.start(); Z.start(); } } 

    AtomicReference подходит для вашего требования.

    Из java-документации об атомном пакете:

    Небольшой набор инструментов для classов, которые поддерживают незакрепленное streamобезопасное программирование для отдельных переменных. По сути, classы в этом пакете расширяют понятие летучих значений, полей и элементов массива тем, которые также предоставляют операцию условного обновления атома формы:

     boolean compareAndSet(expectedValue, updateValue); 

    Образец кода:

     String initialReference = "value 1"; AtomicReference someRef = new AtomicReference(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged); 

    В приведенном выше примере вы заменяете String своим собственным Object

    Связанный вопрос:

    Когда использовать AtomicReference в Java?

    Interesting Posts

    Селектор IE8 css

    Могу ли я установить заголовок по умолчанию для окна tmux?

    Когда следует использовать метод GET или POST? В чем разница между ними?

    UIButton внутри представления с UITapGestureRecognizer

    Как получить подстроку в C #?

    ASP.NET использует SqlConnection connect MySQL

    Каков самый простой способ получить подробный список оборудования в моей Windows-коробке?

    TortoiseGit с ключом openssh не аутентифицируется с использованием ssh-agent

    Не удается загрузить / установить Internet Explorer 9 и Live Essentials 2011

    Почему бы не использовать java.util.logging?

    Исключения фильтров блокировки всплывающих окон Firefox

    Использование двух значений для одного оператора case switch

    Добавить элемент в контекстное меню значка, прикрепленного к панели задач в Windows 7

    Можно ли использовать телефон Android в качестве устройства ввода для Windows через Bluetooth?

    С учетом информации о мощности на ЭЛТ-мониторе, как я могу рассчитать энергопотребление (кВтч)?

    Давайте будем гением компьютера.