Что такое блокировки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Блокировки (Locks) в Java
Блокировки — это синхронизационные механизмы, которые управляют доступом нескольких потоков к общим ресурсам. Они предотвращают race conditions, когда несколько потоков пытаются одновременно изменять одни данные.
Концепция блокировки
Без блокировок:
public class Counter {
private int value = 0;
public void increment() {
value++; // ПРОБЛЕМА: race condition!
// Поток 1 читает 5
// Поток 2 читает 5
// Оба пишут 6
// Результат: value = 6 вместо 7
}
}
С блокировкой:
public class Counter {
private int value = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) { // одновременно только один поток
value++;
}
}
}
Типы блокировок
1. Synchronized (Мониторы)
Встроенный механизм Java, простейший вид блокировки.
public synchronized void method() {
// Заблокирован весь метод
// Только один поток за раз может выполнять этот метод
}
Эквивалентно:
public void method() {
synchronized(this) {
// блокировка на объекте this
}
}
Проблема synchronized:
- Не можно выставить timeout
- Не можно отменить
- Нет условных переменных (условное ожидание)
2. ReentrantLock (Явная блокировка)
Из java.util.concurrent.locks, более гибкая.
public class Counter {
private int value = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
value++;
} finally {
lock.unlock(); // ВАЖНО: всегда в finally
}
}
public boolean tryIncrement() {
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
value++;
return true;
} finally {
lock.unlock();
}
}
return false; // не удалось заблокировать за 100ms
}
}
Преимущества ReentrantLock:
- Timeout поддержка
- Может прерваться (interruption)
- Условные переменные (Condition)
- Reentrancy: один поток может заблокировать несколько раз
3. ReadWriteLock (Читающие/Пишущие блокировки)
Несколько потоков могут читать одновременно, но запись требует исключительный доступ.
public class UserRepository {
private Map<Integer, User> users = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public User getUser(int id) {
lock.readLock().lock();
try {
return users.get(id); // много потоков могут читать
} finally {
lock.readLock().unlock();
}
}
public void saveUser(User user) {
lock.writeLock().lock();
try {
users.put(user.getId(), user); // только один поток может писать
} finally {
lock.writeLock().unlock();
}
}
}
Когда использовать: много читателей, редкие записи (например, кэш конфигурации).
4. Semaphore (Семафор)
Ограничивает количество потоков, получивших доступ к ресурсу.
public class ConnectionPool {
private final Semaphore semaphore = new Semaphore(10); // максимум 10 соединений
public Connection getConnection() throws InterruptedException {
semaphore.acquire(); // занять место
try {
return createConnection();
} finally {
semaphore.release(); // освободить место
}
}
}
5. CountDownLatch
Один поток ждёт, пока N других потоков завершат свою работу.
public class DownloadManager {
public static void main(String[] args) throws InterruptedException {
int fileCount = 5;
CountDownLatch latch = new CountDownLatch(fileCount);
for (int i = 0; i < fileCount; i++) {
new Thread(() -> {
try {
System.out.println("Загрузка файла..." + Thread.currentThread().getName());
Thread.sleep(2000);
} finally {
latch.countDown(); // уменьшить счётчик
}
}).start();
}
latch.await(); // ждать, пока счётчик не будет 0
System.out.println("Все файлы загружены!");
}
}
6. CyclicBarrier
N потоков ждут друг друга до определённой точки.
CyclicBarrier barrier = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " подошёл к барьеру");
try {
barrier.await(); // ждать, пока все три потока не подойдут
} catch (Exception e) {}
System.out.println(Thread.currentThread().getName() + " прошёл барьер");
}).start();
}
// Выведет:
// Thread-0 подошёл к барьеру
// Thread-1 подошёл к барьеру
// Thread-2 подошёл к барьеру
// Все три потока одновременно пройдут дальше
Deadlock (Взаимная блокировка)
Два потока ждут друг друга:
public class DeadlockExample {
Object lock1 = new Object();
Object lock2 = new Object();
public void method1() {
synchronized(lock1) {
synchronized(lock2) { // ждёт lock2
// code
}
}
}
public void method2() {
synchronized(lock2) {
synchronized(lock1) { // ждёт lock1
// code
}
}
}
}
Условия deadlock:
- Несколько ресурсов
- Потоки держат один и ждут другой
- Циклическая зависимость
- Невозможно отнять ресурс
Предотвращение:
- Всегда блокировать в одном порядке
- Использовать timeout
- Избегать вложенных блокировок
Volatile переменная
Лёгкая альтернатива блокировке для флагов:
public class Task implements Runnable {
private volatile boolean running = true; // видима всем потокам
public void stop() {
running = false; // все потоки видят изменение
}
@Override
public void run() {
while (running) {
// работа
}
}
}
volatile НЕ заменяет блокировку для complex операций.
Практический пример: Thread-safe счётчик
public class ThreadSafeCounter {
private final AtomicInteger value = new AtomicInteger(0);
public void increment() {
value.incrementAndGet(); // атомарная операция
}
public int getValue() {
return value.get();
}
}
AtomicInteger — встроенный класс для простых атомарных операций, лучше чем ReentrantLock.
Лучшие практики
- Используй ReentrantLock вместо synchronized для сложных сценариев
- Не вкладывай блокировки — риск deadlock
- Всегда освобождай в finally — даже если исключение
- Используй concurrent. классы* — ConcurrentHashMap, CopyOnWriteArrayList
- Избегай блокировок, где возможно — используй immutable объекты
- Профилируй contention — узкие места с блокировками
Вывод: Блокировки критичны для многопоточности. Неправильное использование приводит к race conditions и deadlocks. Используй современные инструменты из java.util.concurrent.