← Назад к вопросам
Какая проблема возникнет при десяти потоках, выполняющих synchronized?
2.3 Middle🔥 171 комментариев
#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблемы при десяти потоках, выполняющих synchronized блок
Когда несколько потоков конкурируют за один synchronized блок, возникает ряд проблем с производительностью и поведением. Это критическое знание для многопоточного программирования.
1. Монопольный доступ (Lock Contention)
Текущее состояние:
Thread 1: ████ (входит в synchronized, получает блокировку)
Thread 2: ░░░░ (ждёт)
Thread 3: ░░░░ (ждёт)
Thread 4: ░░░░ (ждёт)
...
Thread 10: ░░░░ (ждёт)
Одновременно выполнять может только ОДИН поток!
Остальные ждут в очереди на монитор объекта
2. Пример проблемы
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 1 наносекунда
}
public int getCount() {
return count;
}
}
// Создаём 10 потоков, каждый вызывает increment() 100000 раз
Counter counter = new Counter();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
for (int j = 0; j < 100000; j++) {
counter.increment(); // Все конкурируют за один монитор
}
});
}
// Результат: вместо параллелизма — 90% времени тратится на ожидание блокировки
3. Проблема 1: Снижение производительности (Performance Degradation)
Без потоков: 1 поток выполняет 1M операций
Время: 100ms
С 10 потоками synchronized:
- Каждый поток выполняет в среднем 1/10 работы = 100K операций
- Но вместо параллелизма происходит очередность
- Один поток исполняется, 9 ждут
- Реальное время: 1000ms (10x медленнее!)
Параллелизм ОТСУТСТВУЕТ
4. Проблема 2: Context Switching (переключение контекста)
// ОС переключает потоки тысячи раз в секунду
// Каждое переключение = потеря кэша CPU
Thread t1: работает 0.1ms → сохраняет состояние
Thread t2: работает 0.1ms → сохраняет состояние
...
Thread t10: работает 0.1ms → сохраняет состояние
(вернулся к t1)
// Кэш процессора потерялся!
// L1 cache: 32-64 KB per core
// Context switch: ~1000 циклов процессора
5. Проблема 3: Priority Inversion (инверсия приоритетов)
public synchronized void criticalMethod() {
// ПОЛНАЯ БЛОКИРОВКА на время выполнения
// Даже high-priority потоки ждут!
}
// Сценарий
Thread 1 (приоритет 10): выполняет synchronized
Thread 2 (приоритет 1): ЖДЁТ БЛОКИРОВКИ
Thread 3 (приоритет 10): ЖДЁТ БЛОКИРОВКИ
// Потоки с высоким приоритетом заблокированы низкоприоритетным потоком
6. Проблема 4: Deadlock (взаимная блокировка)
// Объект A и B, каждый со своей блокировкой
Object objA = new Object();
Object objB = new Object();
// Thread 1
synchronized (objA) {
System.out.println("T1: locked A");
Thread.sleep(100);
synchronized (objB) { // Ждёт B!
System.out.println("T1: locked B");
}
}
// Thread 2
synchronized (objB) {
System.out.println("T2: locked B");
Thread.sleep(100);
synchronized (objA) { // Ждёт A!
System.out.println("T2: locked A");
}
}
// DEADLOCK! Оба потока ждут друг друга
// Программа зависает навсегда
7. Проблема 5: Starvation (голодание потока)
public synchronized void method() {
// Если это долгая операция, остальные потоки могут вообще не получить доступ
for (int i = 0; i < 1000000; i++) {
doHeavyWork(); // 10 секунд
}
}
// Результат
// Thread 1: 0-10s работает
// Thread 2: 10-20s работает
// ...
// Thread 10: 90-100s ждал, потом работает
// = 50+ секунд среднего ожидания
8. Мониторинг проблемы
public class SyncProblemDemo {
private int counter = 0;
public synchronized void slowIncrement() {
counter++;
try {
Thread.sleep(1); // Эмулируем долгую работу
} catch (InterruptedException e) {}
}
public static void main(String[] args) throws InterruptedException {
SyncProblemDemo demo = new SyncProblemDemo();
long start = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
for (int j = 0; j < 100; j++) {
demo.slowIncrement();
}
});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
long duration = System.currentTimeMillis() - start;
System.out.println("Total time: " + duration + "ms");
// Ожидаемо: ~1000ms (10 потоков * 100 операций * 1ms)
// Реально: ~1100ms (плюс overhead синхронизации)
// БЕЗ синхронизации было бы: ~100ms (параллелизм)
}
}
9. Решение 1: Lock-free структуры данных
// Вместо synchronized — AtomicInteger
AtomicInteger counter = new AtomicInteger(0);
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
for (int j = 0; j < 100000; j++) {
counter.incrementAndGet(); // Compare-and-swap без блокировки
}
});
}
// Результат: 10x быстрее (реальный параллелизм)
10. Решение 2: ReentrantLock с гранулярностью
public class OptimizedCounter {
private static final int BUCKETS = 10;
private final int[] counts = new int[BUCKETS];
private final Lock[] locks = new ReentrantLock[BUCKETS];
public OptimizedCounter() {
for (int i = 0; i < BUCKETS; i++) {
locks[i] = new ReentrantLock();
}
}
public void increment(int id) {
int bucket = id % BUCKETS; // Распредели нагрузку на 10 бакетов
locks[bucket].lock();
try {
counts[bucket]++;
} finally {
locks[bucket].unlock();
}
}
}
// Теперь 10 потоков конкурируют за 10 разных локов
// Реальный параллелизм!
11. Решение 3: StampedLock (Java 8+)
public class OptimizedData {
private StampedLock lock = new StampedLock();
private int value = 0;
// Оптимистичное чтение (без блокировки!)
public int read() {
long stamp = lock.tryOptimisticRead();
int result = value;
if (!lock.validate(stamp)) {
// Перечитаем если были изменения
stamp = lock.readLock();
try {
result = value;
} finally {
lock.unlockRead(stamp);
}
}
return result;
}
// Запись с полной блокировкой
public void write(int v) {
long stamp = lock.writeLock();
try {
value = v;
} finally {
lock.unlockWrite(stamp);
}
}
}
12. Таблица сравнения подходов
┌────────────────────┬──────────────┬────────────────┬──────────┐
│ Подход │ Тип │ Производ. │ Сложност │
├────────────────────┼──────────────┼────────────────┼──────────┤
│ synchronized │ Pessimistic │ Низкая 10x+ │ Низкая │
│ AtomicInteger │ Lock-free │ Хорошая 2x │ Средняя │
│ ReentrantLock │ Explicit │ Хорошая 5x │ Высокая │
│ StampedLock │ Optimistic │ Отличная 1x │ Высокая │
│ immutable objects │ Keine Locks │ Отличная 1x │ Средняя │
└────────────────────┴──────────────┴────────────────┴──────────┘
Итоги: проблемы synchronized с 10 потоками
- Lock Contention — все ждут одного монитора
- Performance Degradation — параллелизм становится последовательностью
- Context Switching — потеря кэша процессора
- Deadlock Риск — взаимные блокировки
- Starvation — некоторые потоки могут не получить доступ
- Priority Inversion — низкоприоритетный поток блокирует высокоприоритетный
Рекомендация:
- Для простых операций: AtomicInteger
- Для сложных логик: ReentrantLock или StampedLock
- Для избежания проблем: immutable objects или message passing