В чем разница между synchronized и Lock (ReentrantLock) в Java?

Ответ

Оба механизма обеспечивают взаимное исключение (mutual exclusion) в многопоточности, но Lock (из пакета java.util.concurrent.locks) предоставляет более гибкий и расширенный API.

synchronized (ключевое слово)

  • Простота: Встроено в язык, синтаксис лаконичен.
  • Управление: Автоматическое. Блокировка освобождается при выходе из блока/метода (даже в случае исключения).
  • Гибкость: Ограниченная. Нет возможности попытаться получить блокировку без ожидания, проверить ее наличие или прервать ожидающий поток.
  • Честность: Не гарантирует порядок получения блокировки (нечестная политика).

Lock (интерфейс), обычно ReentrantLock

  • Простота: Требует явного создания, блокировки (lock()) и, что критически важно, явной разблокировки (unlock()) в блоке finally.
  • Управление: Ручное. Дает больше контроля, но риск забыть unlock().
  • Гибкость: Высокая. Предоставляет методы:
    • tryLock() — попытка получить блокировку без ожидания или с таймаутом.
    • lockInterruptibly() — возможность прервать поток, ожидающий блокировки.
    • Возможность создания условий (Condition) для более сложной синхронизации (await(), signal()).
  • Честность: ReentrantLock можно создать с честной политикой (fairness), которая уменьшает голодание потоков.

Пример:

// 1. С использованием synchronized
public class CounterSync {
    private int count = 0;
    public synchronized void increment() {
        count++; // Блокировка на уровне метода
    }
}

// 2. С использованием ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CounterLock {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); // Явная блокировка
        try {
            count++;
        } finally {
            lock.unlock(); // Гарантированное освобождение в finally
        }
    }

    public boolean tryIncrement() {
        if (lock.tryLock()) { // Пытаемся получить блокировку без ожидания
            try {
                count++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false; // Не удалось заблокировать, делаем что-то еще
    }
}

Когда что использовать:

  • synchronized — для большинства простых случаев, где достаточно базовой блокировки. Более безопасен (автоуправление).
  • Lock — когда нужны продвинутые функции: таймауты, прерываемое ожидание, честность или несколько условий ожидания.

Ответ 18+ 🔞

Давай разберём эту хрень про блокировки, а то народ путается, как последний идиот. Вот смотри, оба подхода вроде как одно и то же делают — не пускают потоки одновременно куда не надо, но разница, блядь, как между молотком и швейцарским ножом.

synchronized — это как молоток, ёпта

  • Проще некуда: Ключевое слово в языке, накатал и забыл. За тебя всё сделают.
  • Самоуправление: Вошёл в блок — заблокировалось, вышел (даже если с исключением вылетел) — разблокировалось. Рукожопство почти исключено.
  • Гибкость — пиздец какая низкая: Хочешь проверить, свободен ли замок, не дожидаясь? Хуй там! Хочешь прервать поток, который ждёт? Ага, щас! Порядок раздачи блокировок? Да кому он нужен, пусть дерутся, как папуасы.
  • Честность: Её нет. Это дикий запад, кто первый вскочил — тот и рулит.

Lock (обычно ReentrantLock) — это уже швейцарский нож

  • Простота — ну так себе: Надо самому объект создать, самому залочить, и, что самое главное, самому разлочить в finally, а то будет висеть до скончания времён, пидарас.
  • Управление ручное: Мощно, но можно и ногу отстрелить, забыв unlock().
  • Гибкость — овердохуищная: Тут тебе и tryLock() — «а дай-ка попробую, не получится — пойду другим путём», и lockInterruptibly() — «ой, передумал ждать, отпустите!», и даже условия (Condition) можно накрутить для сложной синхронизации, чтоб потоки друг другу сигналы слали.
  • Честность: Можно создать с флагом честности (fairness), тогда блокировки будут раздаваться по очереди, как в советской поликлинике. Медленнее, но справедливее.

Смотри, как это в коде выглядит:

// 1. Вариант для ленивых — synchronized
public class CounterSync {
    private int count = 0;
    public synchronized void increment() {
        count++; // Всё, блядь, приехали. Никто кроме нас.
    }
}

// 2. Вариант для контролирующих перфекционистов — ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CounterLock {
    private int count = 0;
    private final Lock lock = new ReentrantLock(); // Вот он, красавец

    public void increment() {
        lock.lock(); // Взял в ежовые рукавицы
        try {
            count++;
        } finally {
            lock.unlock(); // ОБЯЗАТЕЛЬНО в finally! Иначе пиши пропало.
        }
    }

    // А вот и фишка tryLock — не стоишь как идиот, если занято
    public boolean tryIncrement() {
        if (lock.tryLock()) { // А можно мне? Быстро проверили.
            try {
                count++;
                return true; // Получилось, красава!
            } finally {
                lock.unlock();
            }
        }
        return false; // Не фартануло, пошёл делать другие дела
    }
}

Так когда что брать, блядь?

  • Бери synchronized, когда задача проще пареной репы. Накатал, работает, голова не болит. Идеально для 90% случаев.
  • Хватай Lock, когда начинается высший пилотаж: нужны таймауты, прерывания, честная очередь или эти ваши хитрые Condition. Но помни — с великой силой приходит и великая ответственность не накосячить с разблокировкой.