Gymterview
middle

Перечислите принципы, которым вы следуете в многопоточном программировании

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

1. Давайте значимые имена потокам. OrderProcessor, QuoteProcessor значительно информативнее, чем Thread-1, Thread-2. При отладке, анализе дампов потоков и мониторинге это экономит часы.

Пример
Thread thread = new Thread(task, "order-processor-1");
// или
ExecutorService pool = Executors.newFixedThreadPool(4,
    new ThreadFactoryBuilder().setNameFormat("order-worker-%d").build());

2. Минимизируйте область синхронизации. Блокировка затратна, переключение контекста ещё дороже. Синхронизируйте только критическую секцию минимального размера. Синхронизированный блок предпочтительнее синхронизированного метода.

3. Предпочитайте синхронизаторы вместо wait()/notify(). CountDownLatch, Semaphore, CyclicBarrier, Phaser, Exchanger — проще, надёжнее и поддерживаемее, чем ручное управление через wait()/notify().

4. Предпочитайте Concurrent Collections вместо Synchronized Collections. ConcurrentHashMap масштабируется значительно лучше, чем Collections.synchronizedMap(). Concurrent-коллекции используют CAS, сегментную блокировку и copy-on-write — более тонкие механизмы, чем единый мьютекс.

5. Тщательно обрабатывайте прерывания. InterruptedException — это сигнал потоку завершиться корректно. Его нельзя «проглатывать»:

Пример
// ПЛОХО
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // проглочено — поток не знает, что его прервали
}

// ХОРОШО
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // Восстанавливаем флаг
    // Выполняем корректное завершение
}

6. Обрабатывайте исключения в потоках. Пулы потоков «проглатывают» исключения из Runnable. Оборачивайте код в try-catch или используйте Callable с проверкой Future.get(). Устанавливайте Thread.UncaughtExceptionHandler для перехвата непойманных исключений.

7. Предпочитайте неизменяемые объекты. Immutable-объекты безопасны для многопоточного доступа без синхронизации. Чем больше общего состояния неизменяемо, тем проще и безопаснее код.

8. Используйте final поля для безопасной публикации. JMM гарантирует, что значение final-поля будет видимо всем потокам после завершения конструктора. Это простейший способ безопасной публикации объекта.

9. Избегайте вложенных блокировок — это путь к deadlock. Если избежать невозможно, всегда захватывайте блокировки в одном и том же порядке во всех потоках.

10. Документируйте политику потокобезопасности. Используйте аннотации @ThreadSafe, @NotThreadSafe, @GuardedBy (из JSR-305 / javax.annotation.concurrent) для явного документирования.

11. Для Java 21+: предпочитайте Virtual Threads для IO-bound задач, ReentrantLock вместо synchronized при длительных операциях (во избежание pinning), ScopedValue вместо ThreadLocal.

На собеседовании это открытый вопрос, который проверяет практический опыт. Перечислите 4-5 принципов с конкретными примерами из своего опыта, и объясните, какие проблемы они решают.