RAII in Java

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Получение ресурса есть инициализация (Resource Acquisition Is Initialization, RAII) - это концепция разработки, введенная в C++ Бьярне Страуструпом для безопасного управления ресурсами при обработке исключений. Благодаря сборке мусора Java не обладает этой функцией, но мы можем реализовать нечто похожее, используя try-with-resources.

Проблема, которую решает RAII, очевидна; взгляните на этот код (я уверен, вы знаете, что такое Semaphore и как он работает в Java):

Код довольно примитивен и не делает ничего полезного, но вы, скорее всего, понимаете идею: метод print(), если вызывается из нескольких параллельных потоков, позволяет печатать только пять из них одновременно. Иногда он не позволяет некоторым из них печатать и вызывает исключение, если x больше 1000.

Проблема с этим кодом состоит в утечке ресурсов. Каждый вызов print() с x больше 1000 забирает одно разрешение из семафора и не возвращает его обратно. При пяти вызовах с исключениями семафор будет пустым, и все остальные потоки не будут ничего печатать.

Какое решение? Вот оно:

Мы должны освободить разрешение перед тем, как вызвать исключение.

Однако возникает еще одна проблема: дублирование кода. Мы освобождаем разрешение в двух местах. Если мы добавим больше инструкций throw, нам также придется добавить больше вызовов sem.release().

Очень элегантное решение было представлено в C++ и называется RAII. Вот как оно будет выглядеть в Java:

Посмотрите, как красив код внутри метода Foo.print(). Мы просто создаем экземпляр класса Permit и он сразу получает новое разрешение на семафоре. Затем мы выходим из метода print(), либо по исключению, либо обычным способом, и метод Permit.finalize() освобождает разрешение.

Элегантно, не так ли? Да, это так, но это не сработает в Java.

Это не сработает, потому что, в отличие от C++, Java не уничтожает объекты, когда область их видимости закрывается. Объект класса Permit не будет уничтожен, когда мы выйдем из метода print(). Он будет уничтожен в конечном итоге, но мы не знаем, когда именно. Скорее всего, он будет уничтожен значительно позже, чем все разрешения на семафоре будут получены и мы заблокируемся.

В Java также есть решение. Оно не так элегантно, как в C++, но оно работает. Вот оно:

Обратите внимание на блок try и на интерфейс Closeable, который теперь реализует класс Permit. Объект p будет “закрыт” при выходе из блока try. Выход может произойти либо в конце, либо с помощью операторов return или throw. В любом случае будет вызван метод Permit.close(): так работает try-with-resources в Java.

Я добавил метод acquire() и переместил sem.acquire() из конструктора Permit, потому что я считаю, что конструкторы должны быть свободны от кода.

В заключение, RAII - идеальный подход к проектированию, когда речь идет о ресурсах, которые могут утечь. Несмотря на то, что в Java это не предусмотрено “из коробки”, мы можем реализовать его с помощью try-with-resources и Closeable.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-28 at 14:36

sixnines availability badge   GitHub stars