Когда-то давно, серфясь по исходникам стандартной библиотеки я был озадачен, почему сановские инженеры частенько используют для синхронизации приватные переменные, типа private final Object lock = new Object(), вместо this, или просто объявления метода как synchronized? Ладно бы еще им нужно было несколько разных блокировок -- но такая конструкция встречалась и там, где блокировка всего одна на объект. Потом я где-то прочитал, что синхронизация на this может нарушать инкапсуляцию объекта, поскольку в общем случае состояние монитора является частью состояния объекта, а если монитор -- сам объект, так ведь он доступен всем, кому не лень. Сходу я не смог подобрать пример возможных косяков, но в памяти отложилось. А недавно повторял основы синхронизации, и у меня родился такой пример. Хочу поделиться с сообществом -- вдруг для кого будет интересным.
Итак, у нас есть класс Runner:
copy to clipboardподсветка кода- public class Runner {
-
- private Thread owner = null;
-
- public synchronized void run( final Callable task ) throws Exception {
- owner = Thread.currentThread();
- try {
- task.call();
- } finally {
- owner = null;
- }
- }
-
- public synchronized void check() {
- final boolean invariant = ( owner == null ) || ( owner == Thread.currentThread() );
- if ( !invariant ) {
- //can this code be executed?
- throw new AssertionError( "Lock is broken: " + owner + " is owner, but " + Thread.currentThread() + " is here!" );
- }
- }
- }
Вопрос: может ли когда-нибудь выполниться код в строке 18 (выбросится исключение)?
Ответ: да, легко. А именно:
copy to clipboardподсветка кода- final Runner runner = new Runner();
- final Callable task = new Callable() {
- public Object call() throws Exception {
- runner.wait( 2000 );
- return null;
- }
- };
- final Thread thread = new Thread( "runner" ) {
- public void run() {
- try {
- runner.run( task );
- } catch ( Exception e ) {
- throw new RuntimeException( e );
- }
- }
- };
- thread.start();
-
- //ensure thread started
- Thread.yield();
- Thread.sleep( 100 );
- //check the invariant
- runner.check();
Почему? Просто runner.wait(2000) отпускает монитор на время ожидания. Пока время идет -- объект наивен и беззащитен.
Выводы -- прячьте объекты синхронизации :)
Кстати, вопрос к коммьюнити. Я не смог придумать аналогичного примера, когда synchronized(this) может быть обойден, без callback вызовов. Т.е. если класс изнутри себя не вызывает какой-то клиентский код, то похоже, что synchronized(this) вполне надежен. Или есть варианты?