Могу ли я предположить, что «нет гонки данных» между пользовательским потоком и завершением потока в Java

Рассмотрим следующий класс Java:

class X {
    public void foo() {
        bar = 1;
    }

    protected void finalize() {
        if (bar == 1)
            baz();
    }

    private int bar = 0;
}

Если предположить, что X.foo() никогда не вызывается из какого-либо метода finalize() (прямо или косвенно), могу ли я быть уверен, что приведенный выше код свободен от гонок данных, то есть могу ли я быть уверен, что X.finalize() видит значение, записанное X.foo() в каждом случай, когда X.foo() действительно называется?

Наивный анализ сказал бы, что X.finalize() не может работать одновременно с X.foo() (из-за упомянутого предположения), поэтому дополнительная синхронизация не требуется.

Я бы предположил, что приведенный выше код свободен от гонок данных, но меня беспокоит, что спецификация языка содержит следующий явный оператор в §17.4.5, но ничего не говорит о взаимосвязи между finalize() и методами в целом:

Существует край «происходит до» от конца конструктора объекта до начала финализатора (§12.6) для этого объекта.

EDIT: я вижу необходимость уточнить свой вопрос, поэтому вот попытка точной переформулировки вопроса:

Гарантирует ли Java отношение происходит до между определенными методами X.foo() и X.finalize(), если я гарантирую, что X.foo() никогда не вызывается (прямо или косвенно) из какого-либо метода finalize()? Здесь случается-до следует интерпретировать точно так, как определено в §17.4.5.


person Kristian Spangsege    schedule 12.12.2013    source источник
comment
Что, если финализатор содержит другой код перед if? В общем случае этот код может снова вернуть объект в область видимости, что может означать, что другие потоки снова получат к нему доступ. Но я совершенно уверен в этом; Мне очень интересно узнать, сможет ли кто-нибудь подтвердить или опровергнуть мой комментарий.   -  person Viktor Seifert    schedule 12.12.2013
comment
Вы поднимаете очень важный момент, о котором я не был достаточно осведомлен. Случай, когда X.finalize() содержит код, который воскрешает экземпляр, мне не интересен (потому что я контролирую эту часть кода и знаю, что ничего не воскрешаю). Однако кажется, что воскресение может произойти и другими способами, которые я не могу контролировать. Например, приложение может иметь финализатор, который воскрешает экземпляр X.   -  person Kristian Spangsege    schedule 12.12.2013


Ответы (3)


Поскольку каждый поток имеет кеш переменных, он не является потокобезопасным, в других работах каждый поток имеет свое собственное представление переменных. Вы можете применить volatine к переменной, чтобы заставить поток загрузить переменную. То же самое работает, если доступ к переменной осуществляется из блока synchronized.

-> Когда происходит обновление кэша потоков Java?

person Chriss    schedule 12.12.2013
comment
Да, бар лучше определить как волатильный, это точно. Хорошая точка зрения. Я предполагаю, что ОП имел в виду: у меня есть опасения, даже если я определяю их как изменчивые. - person peter.petrov; 12.12.2013
comment
Мой вопрос более общий. Пожалуйста, посмотрите мою попытку переформулировать вопрос. - person Kristian Spangsege; 12.12.2013

Установка/получение значения int в Java в любом случае является атомарным, насколько я знаю.

Так что не думаю, что вам стоит беспокоиться.

"но ничего не говорится об отношении
между finalize() и методами в целом"

Это связано с тем, что эти методы (в общем случае) управляются/вызываются
не JVM, а вами. Так что он ничего не может сказать.

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

ОБНОВЛЕНИЕ:

"Гарантирует ли Java связь между конкретным методом X.foo() и
X.finalize(), если я гарантирую, что X.foo() никогда не вызывается (прямо или косвенно) из
любой финализации? () метод"

Предположим противное -> Представьте ситуацию, которую вы получите.

Метод finalize() был вызван для объекта x; затем кто-то вызывает foo() для этого объекта x (примечание: x уже мертв, т.е. собран мусор). Возможно ли это? Не мне.

person peter.petrov    schedule 12.12.2013
comment
Мой вопрос общий. Думайте о int как о любом типе. - person Kristian Spangsege; 12.12.2013
comment
В общем, получение/установка ссылки на объект также является атомарным; хм, у вас могут быть проблемы с long и double (но я сомневаюсь в этом, потому что ... см. обновленный ответ). - person peter.petrov; 12.12.2013
comment
Мой вопрос более общий. Пожалуйста, посмотрите мою попытку переформулировать вопрос. - person Kristian Spangsege; 12.12.2013
comment
Смотрите мой обновленный ответ. Подумайте об этом, я думаю, вы сами получите ответ. И если я совершенно неправильно понимаю вашу озабоченность - дайте мне знать, пожалуйста. - person peter.petrov; 12.12.2013
comment
Бьюсь об заклад, ваш вывод правильный, но я не думаю, что вы понимаете всю сложность вопроса. Мне нужно посмотреть, решит ли кто-нибудь более тщательно мои проблемы. - person Kristian Spangsege; 12.12.2013
comment
Я понимаю сложность вашего вопроса :) Я просто сомневаюсь, что вы найдете такую ​​​​прямую гарантию (которую вы ищете) в JLS. Но я все еще не совсем понимаю, как вы себе представляете эту ситуацию. Кроме того, я уверен, вы знаете, что отношение JLS hb определяет не полный порядок, а только частичный порядок. А в отношении частичного порядка (как мы знаем из математики) вам вполне может не хватать этой гарантии (которую вы ищете) только потому, что она частичная. Так что я понимаю ваш вопрос даже с формальной точки зрения, но я не понимаю его с практической точки зрения (то есть какой-то пример того, когда это может произойти). - person peter.petrov; 12.12.2013
comment
Итак, если мы не говорим строго о hb, я до сих пор не понимаю, как ваш метод foo() может вызываться после или все еще работать, когда вызывается finalize()... Теперь, когда я написал это, я думаю: почему бы нет? :) Хорошо, да, я тоже не уверен. Я думаю, мой ответ был основан больше на практической стороне вещей. - person peter.petrov; 12.12.2013
comment
Как вы знаете, синхронизация имеет два аспекта: атомарность и соблюдение порядка. Что меня беспокоит здесь, так это соблюдение порядка. Я могу представить себе реализацию JVM, в которой поток GC вызывает X.finalize() каким-то способом без блокировки, который не обеспечивает надлежащего отношения happens-before между последним действием пользователя над объектом и действиями финализатора. Без каких-либо жестких гарантий, прямо или косвенно полученных от JLS, я не вижу возможности предполагать поведение без гонок в моем коде выше. - person Kristian Spangsege; 12.12.2013
comment
Да, я понял. Я склонен согласиться с вами сейчас. Да, если это не в JLS, я думаю, вы не можете этого предположить. - person peter.petrov; 12.12.2013

Нет. У вас не должно быть отношений между foo() и finalize(). В основном это означает, что вы можете увидеть ноль в методе finalize() для панели поля.

Я думаю, что модель памяти Java в основном говорит о том, что каждый поток работает сам по себе и видит действия других потоков в неопределенном и, следовательно, зависящем от платформы порядке. Только с помощью действий, описанных в разделе Происходит до достижения определенного порядка.

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

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

person Thomas Krieger    schedule 15.12.2013