Як вистрілити собі в ногу за допомогою генератора випадкових чисел?


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Попередимо відразу, що тут Ви не знайдете пояснення, як працюють генератори випадкових чисел і чому отримані за їх допомогою числа не такі вже і випадкові. Тут буде тільки теорія, так що, на жаль, прочитавши цю статтю, Ви не навчитеся зламувати ігрові автомати.

Розглянемо проблему, що часто зустрічається при використанні генератора випадкових чисел зі стандартної бібліотеки Java.

Спершу давайте подивимося, як клас java.util.Random працює при використанні декількох потоків. Його об’єкти потокобезпечні, проте одночасне використання того самого об’єкта цього класу в різних потоках синхронізоване. Як ми з’ясували, це часто призводить до значного уповільнення роботи додатка через те, що декілька потоків намагаються одночасно отримати доступ до одного ресурсу.

Спочатку це може сприйматися як нагальна проблема. Як часто Вам потрібно зробити щось непередбачуване у Вашому корпоративному додатку? Чи необхідно неухильно слідувати бізнес-правилам? Слід визнати, що іноді ці так звані “правила” ще більше випадкові, ніж генератори по-справжньому рандомних чисел, але це вже інша історія.

Як завжди, проблема криється в дрібницях, і в даному випадку це підклас java.util.Randomjava.util.SecureRandom. Як випливає з його назви, він застосовується, якщо результат роботи генератора має бути криптографічно стійким. Через нікому невідомі причини об’єкти цього класу використовуються в ситуаціях, коли криптографічна стійкість, здавалося б, не має особливого значення.

Ми помітили цю неприємну особливість, коли розробляли інструмент для автоматичного виявлення проблем взаємодії потоків і блокувань. Судячи з результатів виконаної нами роботи, одна з найчастіших проблем виникає при виклику методу java.io.File.createTempFile (). Він використовує клас SecureRandom, щоб визначити ім’я створюваного файлу. Давайте подивимось на його код:

private static final SecureRandom random = new SecureRandom ();static File generateFile (String prefix, String suffix, File dir) { long n = random.nextLong (); if (n == Long.MIN_VALUE) { n = 0; // крайній випадок
} else { n = Math.abs (n); } return new File (dir, prefix + Long.toString (n)  + suffix);}

При виклику nextLong () об’єкт класу SecureRandom використовує метод nextBytes (), який оголошений як синхронізований:

synchronized public void nextBytes (byte[] bytes) { secureRandomSpi.engineNextBytes (bytes);}

Можна сказати, якщо створити новий екземпляр SecureRandom у кожному потоці, то жодних проблем не виникне. На жаль, не все так просто. SecureRandom застосовує абстрактний клас java.security.SecureRandomSpi, і всі екземпляри будуть звертатися до нього, тому утруднень не уникнути (триває обговорення цього багу, є тести продуктивності при ньому).

Усе це за певних обставин (особливо якщо Ваший додаток використовує SSL-з’єднання, які покладаються на SecureRandom при здійсненні хендшейка), обов’язково спричинить довгостроковий спад продуктивності.

Виправити ситуацію у разі, якщо Ви можете змінювати код додатка, легко – достатньо використати java.util.ThreadLocalRandom в умовах багатопотоковості. Проте може статися і так, що проблема криється в стандартному API, тоді серйозного рефакторингу не уникнути.

У чому мораль цієї історії? Працювати з потоками слід обережно. Особливо якщо Ваші інструменти для розробки це не враховують. У будь-якому випадку, сподіваємося, що ця стаття запобіжить появі бібліотек, де генерація випадкових чисел стане каменем спотикання.

Переклад статті “Shooting yourself in foot with Random number generators”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *