Resource Acquisition Is Initialization ( RAII ) — идея проекта, предложенная Бьярном Страуструпом в C ++ для безопасного управления ресурсами в исключительных ситуациях. Благодаря сборке мусора Java не имеет этой возможности, но мы можем реализовать нечто подобное, используя try-with-resources .
Проблема, которую RAII решает, очевидна; взгляните на этот код (я уверен, что вы знаете, что такое Semaphore
и как он работает в Java):
01
02
03
04
05
06
07
08
09
10
11
|
class Foo { private Semaphore sem = new Semaphore( 5 ); void print( int x) throws Exception { this .sem.acquire(); if (x > 1000 ) { throw new Exception( "Too large!" ); } System.out.printf( "x = %d" , x); this .sem.release(); } } |
Код довольно примитивен и не делает ничего полезного, но вы, скорее всего, поняли идею: метод print()
, при вызове из нескольких параллельных потоков, позволит только пяти из них печатать параллельно. Иногда это не позволяет некоторым из них печатать и выдает исключение, если x
больше 1000
.
Проблема с этим кодом — утечка ресурсов . Каждый вызов print()
с x
больше 1000
получает одно разрешение от семафора и не возвращает его. В пяти вызовах с исключениями семафор будет пустым, и все остальные потоки ничего не будут печатать.
Каково решение? Вот:
01
02
03
04
05
06
07
08
09
10
11
12
|
class Foo { private Semaphore sem = new Semaphore( 5 ); void print( int x) throws Exception { this .sem.acquire(); if (x > 1000 ) { this .sem.release(); throw new Exception( "Too large!" ); } System.out.printf( "x = %d" , x); this .sem.release(); } } |
Мы должны отпустить разрешение, прежде чем мы бросим исключение.
Однако возникает еще одна проблема: дублирование кода. Мы отпускаем разрешение в двух местах. Если мы добавим больше инструкций throw
нам также придется добавить больше sem.release()
.
Очень элегантное решение было введено в C ++ и называется RAII. Вот как это будет выглядеть в Java:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
class Permit { private Semaphore sem; Permit(Semaphore s) { this .sem = s; this .sem.acquire(); } @Override public void finalize() { this .sem.release(); } } class Foo { private Semaphore sem = new Semaphore( 5 ); void print( int x) throws Exception { new Permit( this .sem); if (x > 1000 ) { throw new Exception( "Too large!" ); } System.out.printf( "x = %d" , x); } } |
Посмотрите, насколько красив код внутри метода Foo.print()
. Мы просто создаем экземпляр класса Permit
и он сразу же получает новое разрешение на семафор. Затем мы выходим из метода print()
либо по исключению, либо обычным способом, и метод Permit.finalize()
освобождает разрешение.
Элегантно, не правда ли? Да, это так, но это не будет работать в Java.
Это не будет работать, потому что, в отличие от C ++, Java не уничтожает объекты, когда их область видимости закрыта. Объект класса Permit
не будет уничтожен при выходе из метода print()
. Это будет уничтожено в конце концов, но мы не знаем когда именно. Скорее всего, он будет уничтожен после того, как все разрешения в семафоре будут получены, а мы заблокированы.
В Java тоже есть решение. Он не так элегантен, как в C ++, но работает. Вот:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Permit implements Closeable { private Semaphore sem; Permit(Semaphore s) { this .sem = s; } @Override public void close() { this .sem.release(); } public Permit acquire() { this .sem.acquire(); return this ; } } class Foo { private Semaphore sem = new Semaphore( 5 ); void print( int x) throws Exception { try (Permit p = new Permit( this .sem).acquire()) { if (x > 1000 ) { throw new Exception( "Too large!" ); } System.out.printf( "x = %d" , x); } } } |
Обратите внимание на блок try
и на интерфейс Closeable
который теперь реализует класс Permit
. Объект p
будет «закрыт» при выходе из блока try
. Он может выйти либо в конце, либо с помощью операторов return
или throw
. В любом случае Permit.close()
будет вызван: так работает try-with-resources в Java.
Я ввел метод sem.acquire()
acquire()
и переместил sem.acquire()
из конструктора Permit
поскольку считаю, что конструкторы должны быть свободны от кода.
Подводя итог, RAII это идеальный дизайн шаблон подход, когда вы имеете дело с ресурсами, которые могут утечь . Несмотря на то, что в Java его нет «из коробки», мы можем реализовать его с помощью try-with-resources и Closeable
.
Ссылка: | RAII на Java от нашего партнера по JCG Егора Бугаенко в блоге About Programming . |