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