Можно ли сломать synchronized(this)?

Mar 18, 2010 15:41

Когда-то давно, серфясь по исходникам стандартной библиотеки я был озадачен, почему сановские инженеры частенько используют для синхронизации приватные переменные, типа private final Object lock = new Object(), вместо this, или просто объявления метода как synchronized? Ладно бы еще им нужно было несколько разных блокировок -- но такая конструкция встречалась и там, где блокировка всего одна на объект. Потом я где-то прочитал, что синхронизация на this может нарушать инкапсуляцию объекта, поскольку в общем случае состояние монитора является частью состояния объекта, а если монитор -- сам объект, так ведь он доступен всем, кому не лень. Сходу я не смог подобрать пример возможных косяков, но в памяти отложилось. А недавно повторял основы синхронизации, и у меня родился такой пример. Хочу поделиться с сообществом -- вдруг для кого будет интересным.



Итак, у нас есть класс Runner:

copy to clipboardподсветка кода
  1. public class Runner {  
  2.   
  3.     private Thread owner = null;  
  4.   
  5.     public synchronized void run( final Callable task ) throws Exception {  
  6.         owner = Thread.currentThread();  
  7.         try {  
  8.             task.call();  
  9.         } finally {  
  10.             owner = null;  
  11.         }  
  12.     }  
  13.   
  14.     public synchronized void check() {  
  15.         final boolean invariant = ( owner == null ) || ( owner == Thread.currentThread() );  
  16.         if ( !invariant ) {  
  17.             //can this code be executed?  
  18.             throw new AssertionError( "Lock is broken: " + owner + " is owner, but " + Thread.currentThread() + " is here!" );  
  19.         }  
  20.     }  
  21. }  


Вопрос: может ли когда-нибудь выполниться код в строке 18 (выбросится исключение)?
Ответ: да, легко. А именно:

copy to clipboardподсветка кода
  1. final Runner runner = new Runner();  
  2. final Callable task = new Callable() {  
  3.     public Object call() throws Exception {  
  4.         runner.wait( 2000 );  
  5.         return null;  
  6.     }  
  7. };  
  8. final Thread thread = new Thread( "runner" ) {  
  9.     public void run() {  
  10.         try {  
  11.             runner.run( task );  
  12.         } catch ( Exception e ) {  
  13.             throw new RuntimeException( e );  
  14.         }  
  15.     }  
  16. };  
  17. thread.start();  
  18.   
  19. //ensure thread started  
  20. Thread.yield();  
  21. Thread.sleep( 100 );  
  22. //check the invariant  
  23. runner.check();  


Почему? Просто runner.wait(2000) отпускает монитор на время ожидания. Пока время идет -- объект наивен и беззащитен.

Выводы -- прячьте объекты синхронизации :)

Кстати, вопрос к коммьюнити. Я не смог придумать аналогичного примера, когда synchronized(this) может быть обойден, без callback вызовов. Т.е. если класс изнутри себя не вызывает какой-то клиентский код, то похоже, что synchronized(this) вполне надежен. Или есть варианты?

java, threads

Previous post Next post
Up