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



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



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)
{
}
}
}


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

634   8  

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

вопрос СЭ:

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

Comments

    Ничего не найдено.