Ответ
Оба механизма обеспечивают взаимное исключение (mutual exclusion) и повторный вход (reentrancy), но ReentrantLock предоставляет более расширенные возможности.
synchronized — встроенное в язык ключевое слово. Синхронизация осуществляется либо на методе (весь метод), либо на объекте в блоке кода.
ReentrantLock — класс из пакета java.util.concurrent.locks, реализующий интерфейс Lock. Он требует явной блокировки и разблокировки.
| Сравнительная таблица: | Критерий | synchronized |
ReentrantLock |
|---|---|---|---|
| Получение блокировки | Неявное, при входе в блок/метод. | Явное, через вызов lock(). |
|
| Освобождение блокировки | Неявное, при выходе из блока/метода (даже при исключении). | Явное, через unlock() (обязательно в finally!). |
|
| Попытка блокировки с таймаутом | Нет. | Есть: tryLock(long timeout, TimeUnit unit). |
|
| Прерываемое ожидание | Нет. Поток, ожидающий блокировку, нельзя прервать. | Есть: lockInterruptibly(). |
|
| Честность (Fairness) | Не гарантируется. | Можно создать с политикой честности (new ReentrantLock(true)), что уменьшает голодание, но снижает пропускную способность. |
|
| Связка условий (Condition) | Одно встроенное условие ожидания (wait(), notify()). |
Можно создать несколько объектов Condition на один lock для более точного управления потоками. |
|
| Гибкость | Базовая, но лаконичная. | Высокая, но требует аккуратного управления. |
Пример synchronized:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Критическая секция защищена
}
// Или блочная синхронизация:
public void decrement() {
synchronized (this) {
count--;
}
}
}
Пример ReentrantLock с таймаутом и условием:
import java.util.concurrent.locks.*;
public class Buffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // Условие "не полон"
private final Condition notEmpty = lock.newCondition(); // Условие "не пуст"
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (bufferIsFull()) {
notFull.await(); // Ждем, пока не освободится место
}
// ... добавить элемент ...
notEmpty.signal(); // Сигнализируем, что буфер не пуст
} finally {
lock.unlock(); // Освобождение в finally ОБЯЗАТЕЛЬНО
}
}
public Object take(long timeout, TimeUnit unit) throws InterruptedException {
lock.lock();
try {
if (!lock.tryLock(timeout, unit)) { // Попытка получить lock с таймаутом
throw new TimeoutException("Не удалось получить блокировку");
}
// ... взять элемент ...
return item;
} finally {
lock.unlock();
}
}
}
Рекомендация: Используйте synchronized для простых случаев, где его возможностей достаточно. Применяйте ReentrantLock, когда необходимы продвинутые функции: таймауты, прерываемые блокировки, несколько условий или честность.
Ответ 18+ 🔞
А, слушай, смотри, вот эти твои замки в Java... Ну, блядь, как же они меня иногда бесят, честное слово. Сидишь такой, думаешь: ну что за ёперный театр, synchronized или ReentrantLock? А оказывается, разница-то — овердохуища!
Короче, оба они, сука, одно и то же делают — не пускают других, пока один в домике. Mutual exclusion, повторный вход — всё это есть. Но один — как тупая дубина, встроенная в язык, а второй — как швейцарский нож с хуевой тучей функций, но если им неаккуратно помахать — себе же палец отрежешь.
synchronized — это типа, блядь, родная, мамина синхронизация. Написал ключевое слово — и всё, ты в бронежилете. Либо на весь метод нацепил, либо в блок кода засунул. Вышел из блока — замок сам упал. Даже если ты внутри, сука, исключение выкинул — всё равно отпустит. Просто, удобно, но туповато.
ReentrantLock — это уже не ключевое слово, а целый класс, надо руками всё делать. lock() позвал — unlock() в finally блоке не забудь, а то пипец, deadlock на раз-два получишь. Зато, блядь, какие возможности!
Вот смотри, табличку нарисовал, чтобы в голове не еблось:
| Критерий | synchronized |
ReentrantLock |
|---|---|---|
| Взял замок как? | Сам взялся, когда в блок вошёл. | Самому крикнуть lock(), явно. |
| Отпустил как? | Сам отпустил, когда вышел. | Самому орать unlock(), и обязательно в finally, а то всех залочишь нахуй! |
| Можно подождать немного? | Не, нихуя. Будешь висеть, пока не дадут. | Ага, tryLock с таймаутом есть. Не дали за время — пошёл нахуй, дальше делай что хочешь. |
| Можно прервать ожидание? | Ни в жисть. Виси и терпи. | Можно, lockInterruptibly() есть. Крикнули тебе interrupt() — ты проснулся и пошёл решать свои проблемы. |
| Честность есть? | Какая, блядь, честность? Кто первый вскочил — того и тапки. | Можно создать честный лок (new ReentrantLock(true)). Тогда кто первый в очередь встал — тот первый и зайдёт. Но это медленнее, ебать. |
| Условия (Condition) | Одно, родненькое: wait() и notify(). |
А тут, сука, можно на один лок несколько разных Condition навешать! Как в отделениях больницы: одни ждут не пусто, другие — не полно. Красота! |
| Гибкость | Как топор. Нарезал — и ладно. | Как лазерный скальпель. Мощно, но если трясутся руки — себе же хуже сделаешь. |
Вот synchronized, простой как три копейки:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Всё, защищено. Красота.
}
}
А вот ReentrantLock, чтоб ты понимал, на что он способен:
import java.util.concurrent.locks.*;
public class Buffer {
private final Lock lock = new ReentrantLock();
// Создаём две разные очереди ожидания! Это же пиздец как удобно!
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (bufferIsFull()) {
notFull.await(); // Спи, дружок, пока место не появится
}
// ... кладём элемент ...
notEmpty.signal(); // Эй, те, кто ждал не пусто! Просыпайтесь, товар приехал!
} finally {
lock.unlock(); // ОСВОБОДИТЬ В FINALLY! А то всех заебёшь!
}
}
public Object take(long timeout, TimeUnit unit) throws InterruptedException {
lock.lock();
try {
// Пытаемся взять лок, но только N секунд. Не получилось — летит TimeoutException, и не висим тут, блядь, вечно.
if (!lock.tryLock(timeout, unit)) {
throw new TimeoutException("Не удалось получить блокировку, иди нахуй");
}
// ... берём элемент ...
return item;
} finally {
lock.unlock();
}
}
}
Итог, блядь: Если задача простая — херачь synchronized и не мучайся. Если же тебе нужны эти все таймауты, прерывания, честность или хитрая жопа с несколькими условиями ожидания — тогда бери ReentrantLock. Но будь готов, сука, самому за всем следить и не забывать его отпускать, а то доверия к такому коду — ноль ебать.