Ответ
Оба механизма обеспечивают взаимное исключение (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. Но помни — с великой силой приходит и великая ответственность не накосячить с разблокировкой.