Существует ли способ решения проблемы race condition?
Да, существует несколько способов решения. Выбор зависит от конкретного сценария.
Основные способы
1. Синхронизация (synchronized)
Операции над разделяемым ресурсом помещаются в synchronized блок, гарантирующий взаимное исключение и видимость:
Пример
public class SafeCounter {
private int count = 0;
public synchronized void increment() {
count++; // Теперь атомарно
}
public synchronized int getCount() {
return count;
}
}
2. Атомарные переменные
Классы java.util.concurrent.atomic обеспечивают атомарность без блокировок:
Пример
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Атомарная CAS-операция
}
3. volatile + однократная запись
Если переменная записывается одним потоком, а читается многими, достаточно volatile:
Пример
private volatile boolean ready = false;
// Поток-писатель
public void prepareData() {
data = loadData();
ready = true; // Все потоки-читатели увидят это
}
4. Потокобезопасные коллекции
Использование конкурентных коллекций из java.util.concurrent:
Пример
// Вместо HashMap + synchronized
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> expensiveComputation(k)); // Атомарная операция
5. Неизменяемые объекты (Immutable objects)
Объекты, состояние которых нельзя изменить после создания, свободны от race condition по определению:
Пример
public record Point(int x, int y) { }
// Нет сеттеров, нет мутабельного состояния — нет гонок
6. ThreadLocal
Если каждому потоку нужна своя копия переменной:
Пример
private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
Сводная таблица
| Способ | Блокировка | Подходит для | Производительность |
|---|---|---|---|
synchronized |
Да | Сложные составные операции | Средняя |
ReentrantLock |
Да | Составные операции с таймаутом, tryLock | Средняя |
Atomic* |
Нет (lock-free) | Простые read-modify-write | Высокая |
volatile |
Нет | Одна запись — много чтений | Высокая |
| Concurrent-коллекции | Частично | Конкурентный доступ к коллекциям | Высокая |
| Immutable objects | Нет | Передача данных между потоками | Высокая |
ThreadLocal |
Нет | Изоляция данных по потокам | Высокая |
Аналогия из жизни. Проблема: два человека одновременно редактируют один документ. Решения: (1)
synchronized– очередь к одному принтеру, (2)Atomic– каждый исправляет только своё слово атомарно, (3) Immutable – никто не редактирует, а создаёт новую версию, (4)ThreadLocal– у каждого своя копия документа.
На собеседовании. Не ограничивайтесь одним способом – покажите, что знаете несколько подходов и умеете выбирать: для простого флага –
volatile, для счётчика –AtomicInteger, для сложной логики –synchronizedилиReentrantLock. Подчеркните, что лучший способ избежать гонок – правильное проектирование: минимизация разделяемого состояния и использование неизменяемых объектов.