Что такое блокировки (locks) в контексте многопоточности?

Ответ

Блокировки (locks) — это механизмы синхронизации, используемые для управления доступом нескольких потоков к общему ресурсу (критической секции) в параллельном программировании. Их цель — обеспечить взаимоисключение (mutual exclusion), чтобы только один поток в данный момент мог выполнять защищённый код.

Основные типы блокировок в Java:

  1. Встроенная (intrinsic) блокировка через synchronized:

    // Синхронизация на методе
    public synchronized void criticalMethod() { /* ... */ }
    
    // Синхронизация на блоке кода
    Object lock = new Object();
    synchronized(lock) {
        // Критическая секция
    }
  2. Явные (explicit) блокировки из java.util.concurrent.locks:

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    Lock lock = new ReentrantLock();
    
    lock.lock(); // Захват блокировки
    try {
        // Критическая секция
    } finally {
        lock.unlock(); // ВСЕГДА освобождайте блокировку в finally!
    }
Сравнение synchronized и ReentrantLock: Особенность synchronized ReentrantLock
Реентерабельность Да Да
Прерываемое ожидание Нет Да (lockInterruptibly())
Попытка захвата с таймаутом Нет Да (tryLock(timeout, unit))
Честность (fairness) Нет Опционально (конструктор)
Условные переменные Одна (wait()/notify()) Множество (newCondition())

Проблемы, связанные с блокировками:

  • Взаимная блокировка (Deadlock): Два или более потока бесконечно ждут друг друга.
  • Голодание (Starvation): Поток не может получить доступ к ресурсу.
  • Инверсия приоритетов (Priority Inversion): Низкоприоритетный поток удерживает блокировку, нужную высокоприоритетному.

Ответ 18+ 🔞

А, блядь, смотри-ка, про блокировки! Ну это ж классика, ёпта, как Герасим с Муму, только в мире потоков. Сидят эти потоки, как немые крестьяне, и молчат, блядь, пока один в критической секции шарит.

Представь картину: у тебя есть общий унитаз — это твой ресурс. А потоков — дохуя, все хотят посрать. Так вот блокировка (lock) — это дверной крючок изнутри, сука! Один зашёл, защёлкнулся — остальные снаружи тупят, чешут репу и ждут, пока он не выйдет и не спустит воду. Это и есть взаимоисключение, ёбана! Чтобы не было ситуации, когда в одну кабинку впердолились сразу двое — пиздец и срач.

В Джаве, блядь, есть два основных подхода, как этот крючок сделать.

Первый — старый, дедовский, через synchronized. Как Герасим — немой, но сильный. Всё просто, на уровне языка.

Можно весь метод обвесить, чтоб он был как крепкий замок:

public synchronized void criticalMethod() { /* ... */ }

Или, если ты хитрая жопа и хочешь экономить, вешаешь замок на конкретный шкафчик (объект):

Object lock = new Object(); // Вот этот самый шкафчик-замок
synchronized(lock) {
    // А вот тут твоя критическая секция, где всё самое ценное лежит
}

Работает, проверено годами, но туповат, как валенок. Захватил — и молчишь, пока не отпустишь. Прервать его — хуй там, жди, пока сам не кончит.

Второй подход — поумнее, через java.util.concurrent.locks. Это уже как современный кодовый замок с кучей функций.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

Lock lock = new ReentrantLock(); // Вот твой навороченный замок

lock.lock(); // Нажал кнопку "закрыть"
try {
    // Опять же, святая святых — критическая секция
    // Делай что хочешь, пока другие смотрят в камеру видеонаблюдения
} finally {
    lock.unlock(); // ОБЯЗАТЕЛЬНО нажми "открыть"! Иначе все сдохнут в очереди!
}

Фишка в том, что finally — это святое, ёпта! Даже если в критической секции вылетит исключение — unlock() всё равно вызовется. А то бывает, чувак в кабинке взял и помер — и все ходят вокруг да около, терпения ноль ебать.

Так в чём разница, спросишь ты? А я тебе, блядь, табличкой разложу, чтоб понятнее было:

Особенность synchronized (дедовский метод) ReentrantLock (продвинутый замок)
Реентерабельность (чтоб сам себя не заблокировал) Да, можно Да, тоже можно
Прервать ожидание (крикнул "выходи, пожар!") Не, нихуя Да, есть lockInterruptibly()
Попытка захвата с таймаутом (постучал, подождал 5 сек, пошёл в кусты) Не, опять нихуя Да, tryLock(timeout, unit) — красота!
Честность (очередь по порядку, а не кто первый вломился) Нет, бардак Опционально, в конструкторе можно включить
Условные переменные (разные очереди: кто пописал, кто посрал) Одна общая (wait()/notify()) Множество, через newCondition()

Но, сука, с великой силой приходит и великая, блядь, ответственность. Можно так наблокировать всё, что получишь классические проблемы, от которых волосы дыбом.

  • Взаимная блокировка (Deadlock). Это пиздец, ёпта! Два потока, как два упрямых осла. Один держит замок А и хочет Б. Второй держит замок Б и хочет А. И стоят так до скончания времён, ебать мои старые костыли. В рот меня чих-пых!
  • Голодание (Starvation). Какой-нибудь мелкий, невзрачный поток стоит в очереди, а его всё время обгоняют наглые потоки с высоким приоритетом. Он так и помрёт, не дождавшись своего унитаза.
  • Инверсия приоритетов (Priority Inversion). Вообще анекдот! Низкоприоритетный поток (какой-нибудь занюханный скрипт) захватил замок и не отпускает. А его ждёт поток с высшим приоритетом (системный, важный). И этот важный вынужден тупо ждать, пока зассанный снизу не закончит свои делишки. Вот тебе и приоритеты, блядь!

Короче, инструмент мощный, но если без мозгов пользоваться — получишь не многопоточное приложение, а театр абсурда, где все актёры немые и друг друга в озеро кидают. Думай, бошка, когда блокировки расставляешь!