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 . |
