Синхронизация неокончательного поля
предупреждение отображается каждый раз, когда я синхронизирую на поле не окончательного класса. Вот код:
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)
{
}
}
}
Я не уверен, что приведенный выше код является правильным способом синхронизации на неокончательном поле класса. Как я могу синхронизировать не окончательное поле?
8 ответов:
прежде всего, я призываю вас очень постараться справиться с проблемами параллелизма на более высоком уровне абстракции, т. е. решить его с помощью классов из java.утиль.одновременно такие как ExecutorServices, Callables, Futures и т. д.
это, как говорится, ничего неправильно с синхронизацией на неокончательном поле как таковом. Вам просто нужно иметь в виду, что если ссылка на объект изменяется, тот же раздел кода может быть запущен в параллель. Т. е., если один поток выполняет код в синхронизированном блоке, и кто-то называет
setO(...)другой поток может работать тот же synchronized-блок, по один и тот же экземпляр по совместительству.синхронизация на объекте, к которому вам нужен эксклюзивный доступ (или, еще лучше, объект, "охраняющий" его).
Это действительно не очень хорошая идея - потому что ваши синхронизированные блоки уже не действительно синхронизироваться на постоянной основе.
предполагая, что синхронизированные блоки предназначены для обеспечения того, чтобы только один поток одновременно обращался к некоторым общим данным, рассмотрим:
- поток 1 входит в synchronized-блок. Ура - он имеет эксклюзивный доступ к общим данным...
- поток 2 вызывает setO ()
- поток 3 (или еще 2...) входит в синхронизированный блок. ИК! Он думает, что у него есть эксклюзивный доступ к общим данным, но поток 1 все еще работает с ним...
с чего бы тебе хочу это произошло? Может быть, есть некоторые очень специальных ситуациях, где это имеет смысл... но вам придется представить мне конкретный случай использования (наряду с способами смягчения сценария, который я дал выше), прежде чем я буду доволен этим.
Я согласен с одним из комментариев Джон: Вы должны всегда используйте манекен окончательной блокировки при доступе к неокончательной переменной, чтобы предотвратить несоответствия в случае изменения ссылки на переменную. Так что в любом случае и как первое эмпирическое правило:
Правило#1: если поле не является окончательным, всегда используйте манекен (private) final lock.
Причина #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.утиль.параллельный. Или начните понимать все о синхронизации и делать это самостоятельно с вложенными блокировками. ;)
для получения более подробной информации о том, почему синхронизация на неокончательных полях прерывается, загляните в мой тестовый случай: https://stackoverflow.com/a/21460055/2012947
и для более подробной информации, почему вам нужно синхронизировать все из-за ОЗУ и Кешей смотрите здесь:https://stackoverflow.com/a/21409975/2012947
Я не вижу правильного ответа здесь, то есть это совершенно нормально, чтобы сделать это.
Я даже не уверен, почему это предупреждение, нет ничего плохого. JVM гарантирует, что вы получите некоторые допустимый объект обратно (или null) при чтении значения, и вы можете синхронизировать на любой
EDIT: таким образом, это решение (как предложил Джон Скит) может иметь проблему с атомарностью реализации "synchronized(object){}" при изменении ссылки на объект. Я спросил отдельно и по словам г-на Эриксона это не потокобезопасно-см.:входит ли синхронизированный блок atomic?. Так что возьмите его в качестве примера, как этого не делать - со ссылками почему;)
смотрите код, как он будет работать, если synchronised () будет атомарным:
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(); } }
Если
oникогда не изменяется в течение времени существования экземпляраX, вторая версия лучше стиль независимо от того, участвует ли синхронизация.теперь, есть ли что-то не так с первой версией невозможно ответить, не зная, что еще происходит в этом классе. Я бы согласился с компилятором, что он действительно выглядит подверженным ошибкам (я не буду повторять то, что сказали другие).
просто добавляя мои два цента: у меня было это предупреждение, когда я использовал компонент, который создается через конструктор, поэтому его поле не может быть окончательным, потому что конструктор не может принимать параметры. Другими словами, Я был квази-конечного поля без ключевого слова Final.
Я думаю, что именно поэтому это просто предупреждение: вы, вероятно, делаете что-то неправильно, но это может быть также.
AtomicReference костюмы для вашего требования.
из документации java о atomic пакет:
небольшой набор классов, которые поддерживают потокобезопасное Программирование без блокировки на отдельных переменных. По сути, классы в этом пакете расширяют понятие изменчивых значений, полей и элементов массива до тех, которые также обеспечивают атомарную операцию условного обновления форма:
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вопрос СЭ:
Comments