Предупреждение отображается каждый раз, когда я синхронизирую неокончательное поле класса. Вот код:
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)
{
}
}
}
Я не уверен, что приведенный выше код является правильным способом синхронизации в поле неокончательного класса. Как мне синхронизировать не финальное поле?
o
ссылка в момент достижения синхронизированного блока. Если объект, которыйo
ссылается на изменение, может прийти другой поток и выполнить блок синхронизированного кода.this
- я бы рекомендовал создать конечную переменную в классе исключительно для целей блокировки , которая не позволяет кому-либо другому блокировать тот же объект.Это действительно не очень хорошая идея - потому что ваши синхронизированные блоки больше не являются действительно синхронизированы последовательным образом.
Предполагая, что синхронизированные блоки предназначены для обеспечения того, чтобы только один поток имел доступ к некоторым общим данным за раз, рассмотрите:
Почему вы хотите, чтобы это произошло? Может быть, есть какие- то очень специфические ситуации, в которых это имеет смысл ... но вам нужно будет представить мне конкретный вариант использования (вместе со способами смягчения того сценария, который я дал выше), прежде чем я буду доволен Это.
источник
o
) - и на полпути его выполнения запускает мутацию другого списка. Как это было бы хорошей идеей? Я думаю, что мы принципиально не согласны с тем, стоит ли блокировать объекты, которых вы касаетесь другими способами. Я бы предпочел рассуждать о своем коде, не зная, что другой код делает с точки зрения блокировки.Я согласен с одним из комментариев Джона: вы всегда должны использовать фиктивную окончательную блокировку при доступе к не конечной переменной, чтобы предотвратить несоответствия в случае изменения ссылки на переменную. Поэтому в любом случае и в качестве первого практического правила:
Правило №1: Если поле не является окончательным, всегда используйте фиктивную (частную) окончательную блокировку.
Причина №1: Вы удерживаете блокировку и самостоятельно меняете ссылку на переменную. Другой поток, ожидающий вне синхронизированной блокировки, сможет войти в защищенный блок.
Причина №2: вы удерживаете блокировку, а другой поток изменяет ссылку на переменную. Результат тот же: другой поток может войти в защищенный блок.
Но при использовании макета финальной блокировки возникает другая проблема : вы можете получить неверные данные, потому что ваш неокончательный объект будет синхронизироваться с ОЗУ только при вызове 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)
другие потоки. Но он сломается, если вы вызовете другой поток примерно так: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
И для получения более подробной информации, почему вам вообще нужна синхронизация из-за ОЗУ и кешей, смотрите здесь: https://stackoverflow.com/a/21409975/2012947
источник
o
синхронизированным (LOCK), чтобы установить связь «происходит раньше» между установкой и объектом чтенияo
. Я обсуждаю это в своем аналогичном вопросе: stackoverflow.com/questions/32852464/…Я не вижу здесь правильного ответа, то есть это совершенно нормально.
Я даже не уверен, почему это предупреждение, в этом нет ничего плохого. JVM гарантирует, что вы получите какой-то действительный объект обратно (или null), когда вы читаете значение, и вы можете синхронизировать на любом объектом.
Если вы планируете фактически изменять блокировку во время ее использования (в отличие, например, от изменения ее с помощью метода инициализации, прежде чем вы начнете ее использовать), вам необходимо создать переменную, которую вы планируете изменить
volatile
. Затем все, что вам нужно сделать, это синхронизировать как старый, так и новый объект, и вы можете безопасно изменить значениеpublic volatile Object lock;
...
synchronized (lock) { synchronized (newObject) { lock = newObject; } }
Там. Это несложно, написать код с блокировками (мьютексами) на самом деле довольно просто. Написать код без них (код без блокировки) - вот что сложно.
источник
РЕДАКТИРОВАТЬ: Таким образом, это решение (как было предложено Джоном Скитом) может иметь проблему с атомарностью реализации «synchronized (object) {}» при изменении ссылки на объект. Я спросил отдельно, и, по словам г-на Эриксона, это не потокобезопасно - см. Является ли ввод синхронизированного блока атомарным?. Так что возьмите это как пример, как этого НЕ делать - со ссылками, почему;)
Посмотрите код, как бы он работал, если бы 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 об атомарном пакете:
boolean compareAndSet(expectedValue, updateValue);
Образец кода:
String initialReference = "value 1"; AtomicReference<String> someRef = new AtomicReference<String>(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged);
В приведенном выше примере вы заменяете
String
своим собственнымObject
Связанный вопрос SE:
Когда использовать AtomicReference в Java?
источник
Если
o
за время существования экземпляра никогда не меняетсяX
, вторая версия является лучшим стилем, независимо от того, задействована ли синхронизация.Теперь невозможно ответить, не зная, что еще происходит в этом классе, что-то не так с первой версией. Я был бы склонен согласиться с компилятором в том, что он выглядит подверженным ошибкам (я не буду повторять то, что говорили другие).
источник
Просто добавляю свои два цента: у меня было это предупреждение, когда я использовал компонент, экземпляр которого создается дизайнером, поэтому это поле не может быть окончательным, потому что конструктор не может принимать параметры. Другими словами, у меня было квазифинальное поле без ключевого слова final.
Я думаю, поэтому это просто предупреждение: вы, вероятно, делаете что-то не так, но, возможно, это тоже правильно.
источник