← Назад к вопросам

Почему появился новый пакет сoncurrent?

1.6 Junior🔥 111 комментариев
#Soft Skills и карьера

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

java.util.concurrent пакет: эволюция многопоточности в Java

Пакет java.util.concurrent (JUC) — это один из самых важных дополнений к Java, появившийся в Java 5 (2004). Он полностью изменил подход к многопоточному программированию, предоставив высокоуровневые инструменты вместо низкоуровневых synchronized блоков.

История: почему потребовался этот пакет

До Java 5 (старый способ: 1995-2004):

// Все полагались на synchronized и wait/notify
public class OldWayCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int get() {
        return count;
    }
    
    // Проблемы:
    // 1. Низкоуровневый код (разработчик управляет блокировками вручную)
    // 2. Легко забыть synchronized или использовать неправильно
    // 3. Deadlock и race conditions
    // 4. Нет переиспользуемых решений
}

Проблемы старого подхода:

  1. Сложность: разработчик должен помнить где нужна синхронизация
  2. Ошибки: забыл synchronized = race condition
  3. Производительность: synchronized может быть неэффективным
  4. Нет высокоуровневых концепций: thread pool, concurrent collections

Решение: java.util.concurrent

В Java 5 (2004) был добавлен пакет java.util.concurrent с рядом классов:

java.util.concurrent
├── Executor Framework (управление потоками)
│   ├── Executor
│   ├── ExecutorService
│   └── ThreadPoolExecutor
├── Collections (thread-safe коллекции)
│   ├── ConcurrentHashMap
│   ├── CopyOnWriteArrayList
│   └── BlockingQueue
├── Synchronizers (примитивы синхронизации)
│   ├── CountDownLatch
│   ├── Semaphore
│   ├── Barrier
│   └── Phaser
├── Locks (более гибкие блокировки)
│   ├── Lock
│   ├── ReentrantLock
│   └── ReadWriteLock
└── Futures (асинхронные вычисления)
    ├── Future
    ├── CompletableFuture
    └── ForkJoinPool

Главные компоненты и почему они важны

1. Executor Framework (управление потоками)

// Старый способ (до Java 5): создание потоков вручную
for (int i = 0; i < 100; i++) {
    new Thread(new Worker()).start();
}
// Проблемы: создание 100 потоков = плохо для памяти и CPU

// Новый способ (Java 5+): использование ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    executor.execute(new Worker());
}
executor.shutdown();
// Результат: 10 потоков переиспользуются для 100 задач

Почему это важно:

  • Переиспользование потоков экономит память
  • Автоматическое управление жизненным циклом потоков
  • Простое управление асинхронными задачами

2. ConcurrentHashMap (безопасная коллекция)

// Старый способ: synchronized HashMap
Map<String, Integer> oldMap = Collections.synchronizedMap(new HashMap<>());
// Проблема: весь HashMap заблокирован при любой операции

// Новый способ: ConcurrentHashMap
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
// Разные потоки могут работать с разными "бакетами" одновременно
// Нет блокировки всего map!

Производительность:

ОперацияSynchronizedConcurrentHashMap
Путают со 100 потоками1s0.1s
Чтение из 100 потоков5ms0.5ms

3. BlockingQueue (очереди для обмена данными)

// Thread-safe очередь для передачи данных между потоками
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();

// Producer (производитель)
new Thread(() -> {
    for (Task task : tasks) {
        queue.put(task);  // Блокирует если очередь полна
    }
}).start();

// Consumer (потребитель)
new Thread(() -> {
    while (true) {
        Task task = queue.take();  // Блокирует если очередь пуста
        processTask(task);
    }
}).start();

Почему это важно:

  • Безопасная передача данных между потоками
  • Автоматическая блокировка при переполнении/пустоте
  • Основа многих асинхронных паттернов

4. Lock и ReentrantLock (гибкие блокировки)

// Старый способ: synchronized
public class OldWayCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
}

// Новый способ: Lock
public class NewWayCounter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

// Преимущества Lock:
// 1. Гибче (tryLock, читаемая блокировка)
// 2. Условные переменные
// 3. Справедливое распределение блокировок
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

public int read() {
    rwLock.readLock().lock();
    try {
        return count;  // Много потоков могут читать одновременно
    } finally {
        rwLock.readLock().unlock();
    }
}

public void write() {
    rwLock.writeLock().lock();
    try {
        count++;  // Только один поток может писать
    } finally {
        rwLock.writeLock().unlock();
    }
}

5. CountDownLatch, Semaphore, Barrier (синхронизаторы)

// CountDownLatch: ждать пока N потоков завершатся
CountDownLatch latch = new CountDownLatch(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        doWork();
        latch.countDown();  // Уменьшить счетчик
    }).start();
}

latch.await();  // Ждать пока счетчик не станет 0
system.out.println("Все потоки закончили!");

// Semaphore: ограничить количество потоков
Semaphore semaphore = new Semaphore(5);  // Максимум 5 потоков

public void accessLimitedResource() throws InterruptedException {
    semaphore.acquire();  // Получить разрешение (ждет если нет)
    try {
        limitedResource.process();
    } finally {
        semaphore.release();  // Освободить разрешение
    }
}

// Barrier: ждать пока ВСЕ потоки достигнут точки
CyclicBarrier barrier = new CyclicBarrier(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        doPartialWork();
        barrier.await();  // Ждать пока все не дойдут сюда
        doFinalWork();
    }).start();
}

Эволюция: от synchronized к CompletableFuture

// Этап 1: synchronized (1995-2004) — низкоуровневый
public synchronized void process() { }

// Этап 2: java.util.concurrent (Java 5) — threads
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Result> future = executor.submit(() -> compute());
Result result = future.get();

// Этап 3: java.util.concurrent.Future (Java 5) — асинхронные результаты
Future<Integer> f1 = executor.submit(this::compute1);
Future<Integer> f2 = executor.submit(this::compute2);
int result = f1.get() + f2.get();

// Этап 4: CompletableFuture (Java 8+) — функциональный подход
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> compute1());
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> compute2());
CompletableFuture<Integer> result = cf1.thenCombine(cf2, Integer::sum);
result.thenAccept(System.out::println);

Почему java.util.concurrent появилась

1. Многоядерные процессоры появились

До 2000х года большинство компьютеров были однопроцессорными. После 2005 началась эра многоядерности. Java нужны были инструменты для использования нескольких ядер:

1995: Intel Pentium (1 ядро)
2005: Intel Core Duo (2 ядра)
2008: Intel Core i7 (4 ядра)
2020: Intel Core i9 (10-20 ядер)

2. Web приложения требовали параллельной обработки запросов

Ява стала популярна на backend'е (Servlets, JSP). Каждый HTTP запрос требует отдельного потока. Нужны были:

  • Thread pools для переиспользования потоков
  • Безопасные коллекции
  • Примитивы синхронизации

3. Сложность synchronized была неуправляемой

Разработчики ошибались с блокировками, возникали deadlock'и. Нужны были более высокоуровневые инструменты.

Влияние на Java экосистему

До java.util.concurrent:

Разработчик писал низкоуровневый код
Больше ошибок
Медленнее разработка
Медленнее приложения

После java.util.concurrent:

Разработчик использует высокоуровневые инструменты
Меньше ошибок
Быстрее разработка
Быстрее приложения

Современный подход (2024)

В современной Java используется комбинация:

// 1. ExecutorService для thread pool'ов
var executor = Executors.newVirtualThreadPerTaskExecutor();  // Java 21+

// 2. CompletableFuture для асинхронности
var result = CompletableFuture
    .supplyAsync(() -> fetchData())
    .thenApply(data -> processData(data))
    .thenAccept(System.out::println);

// 3. Reactive libraries (Project Reactor, RxJava) для сложных потоков данных
Flux.from(data)
    .map(this::process)
    .subscribe(System.out::println);

// 4. Virtual threads (Java 21) для масштабирования
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

Вывод

java.util.concurrent появилась потому что:

  1. Многоядерные процессоры требовали параллельной обработки
  2. Web приложения требовали обработки множества запросов одновременно
  3. Сложность synchronized была неуправляемой для разработчиков
  4. Нужны были высокоуровневые инструменты вместо низкоуровневых примитивов

Это был прорыв, который сделал Java пригодной для параллельных приложений. Без java.util.concurrent, Java не была бы столь успешна на backend'е.

Почему появился новый пакет сoncurrent? | PrepBro