How I Test My Java Classes for Thread-Safety

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

Я затронул эту проблему в одном из моих последних вебинаров, теперь пришло время объяснить ее в письменной форме. Потокобезопасность - это важное качество классов в языках/платформах, таких как Java, где мы часто используем объекты между потоками. Проблемы, вызванные отсутствием потокобезопасности, очень сложны для отладки, так как они случайны и практически невозможно воспроизвести намеренно. Как вы тестируете свои объекты, чтобы убедиться, что они потокобезопасны? Вот как я это делаю.

Предположим, у нас есть простая книжная полка в памяти:

Сначала мы помещаем книгу туда, и книжная полка возвращает ее идентификатор. Затем мы можем прочитать название книги по ее идентификатору.

Класс, кажется, является потокобезопасным, поскольку мы используем потокобезопасный ConcurrentHashMap вместо более простого и непотокобезопасного HashMap, верно? Давайте попробуем протестировать это:

Тест проходит, но это только однопоточный тест. Давайте попробуем выполнить ту же манипуляцию из нескольких параллельных потоков (я использую Hamcrest).

Сначала я создаю пул потоков с помощью Executors. Затем я отправляю десять объектов типа Callable с помощью submit(). Каждый из них добавит новую уникальную книгу на полку. Все они будут выполнены в некотором непредсказуемом порядке некоторыми из этих десяти потоков из пула.

Затем я получаю результаты их выполнения через список объектов типа Future. Наконец, я вычисляю количество созданных уникальных идентификаторов книг. Если число равно 10, то конфликтов не было. Я использую коллекцию Set, чтобы убедиться, что список идентификаторов содержит только уникальные элементы.

Тест проходит на моем ноутбуке. Однако он не сильно надежен. Проблема здесь заключается в том, что он не проверяет Books из нескольких параллельных потоков. Время, проходящее между вызовами submit(), достаточно велико, чтобы завершить выполнение books.add(). Поэтому на самом деле будет выполняться только один поток одновременно. Мы можем проверить это, немного изменив код:

С помощью этого кода я пытаюсь узнать, насколько часто потоки перекрываются друг с другом и выполняют что-то параллельно. Это никогда не происходит, и overlaps равно нулю. Таким образом, наш тест на самом деле ничего не проверяет. Он просто добавляет десять книг на книжную полку по одной. Если я увеличу количество потоков до 1000, иногда они начинают перекрываться. Но мы хотим, чтобы они перекрывались даже при небольшом их количестве. Чтобы решить эту проблему, нам нужно использовать CountDownLatch:

Теперь каждый поток перед тем, как коснуться книг, ожидает разрешения, предоставленного latch. Когда мы отправляем их все через submit(), они остаются на удержании и ожидают. Затем мы освобождаем замок с помощью countDown(), и они все начинают двигаться одновременно. Теперь, на моем ноутбуке, overlaps равно 3-5, даже когда threads равно 10.

И этот последний assertThat() теперь выдает ошибку! Я не получаю 10 идентификаторов книг, как раньше. Это 7-9, но никогда не 10. Класс, видимо, не является потокобезопасным!

Но прежде чем мы исправим класс, давайте упростим наш тест. Давайте используем RunInThreads (переименовано просто в Threads в последней версии) из Cactoos, который делает точно то же самое, что и мы сделали выше, но под капотом.

Первый аргумент assertThat() - это экземпляр Func (функциональный интерфейс), принимающий AtomicInteger (первый аргумент RunsInThreads) и возвращающий Boolean. Эта функция будет выполнена на 10 параллельных потоках с использованием того же подхода на основе замков, что и показано выше.

Этот RunInThreads кажется компактным и удобным, я уже использую его в нескольких проектах.

Кстати, чтобы сделать Books потокобезопасным, мы просто должны добавить synchronized к его методу add(). Или может быть у вас есть лучшее решение?

P.S. Я узнал все это из книги Java Concurrency in Practice авторов Гойтц и др.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 04:44

sixnines availability badge   GitHub stars