Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Монитор для синхронизации в Java
Монитор — это механизм синхронизации, который позволяет нескольким потокам безопасно получать доступ к общим ресурсам. В Java каждый объект имеет встроенный монитор для управления доступом к своему состоянию.
Основной концепт
В многопоточном приложении несколько потоков могут одновременно работать с одним объектом, что может привести к race conditions (гонкам) и несогласованности данных. Монитор гарантирует, что только один поток может выполнять synchronized код объекта одновременно.
Как работает монитор
public class BankAccount {
private int balance = 1000;
// Без синхронизации — race condition!
public void withdraw(int amount) {
if (balance >= amount) {
balance = balance - amount; // Потокоопасность?
}
}
// С синхронизацией — безопасно
public synchronized void withdrawSafe(int amount) {
if (balance >= amount) {
balance = balance - amount; // Теперь безопасно
}
}
}
Когда поток вызывает synchronized метод:
- Поток пытается захватить монитор объекта (lock)
- Если монитор свободен — поток захватывает его и входит в метод
- Другие потоки ждут в очереди (wait queue)
- После выхода из метода — монитор освобождается
- Следующий поток из очереди может захватить монитор
Synchronized методы
public class Counter {
private int count = 0;
// Синхронизированный метод — монитор захватывается на весь метод
public synchronized void increment() {
count++; // Thread-safe
}
public synchronized int getCount() {
return count; // Thread-safe read
}
// Эквивалентно:
public void incrementManual() {
synchronized(this) {
count++;
}
}
}
Synchronized блоки
Для более гранулярной синхронизации можно использовать synchronized блоки:
public class DataProcessor {
private List<String> data = new ArrayList<>();
private int counter = 0;
public void processData(String item) {
// Дорогостоящее вычисление — без синхронизации
String processed = expensiveOperation(item);
// Только критическая секция синхронизирована
synchronized(this) {
data.add(processed);
counter++;
}
}
// Можно синхронизировать по разным объектам
private Object lock1 = new Object();
private Object lock2 = new Object();
private List<String> list1;
private List<String> list2;
public void addToList1(String item) {
synchronized(lock1) {
list1.add(item);
}
}
public void addToList2(String item) {
synchronized(lock2) {
list2.add(item);
}
}
// Эти методы не блокируют друг друга!
}
Synchronized на статических методах
public class Logger {
private static int messageCount = 0;
// Синхронизация по классу, не по объекту
public static synchronized void log(String message) {
messageCount++;
System.out.println(messageCount + ": " + message);
}
// Эквивалентно:
public static void logManual(String message) {
synchronized(Logger.class) {
messageCount++;
System.out.println(messageCount + ": " + message);
}
}
}
Wait, Notify и Notify All
Мониторы поддерживают методы для координации потоков:
public class ProducerConsumer {
private Queue<String> queue = new LinkedList<>();
private final int CAPACITY = 10;
public synchronized void produce(String item) throws InterruptedException {
// Ждём, пока очередь не освободится
while (queue.size() >= CAPACITY) {
wait(); // Освобождаю монитор и жду сигнала
}
queue.add(item);
notifyAll(); // Пробуждаю потребителей
}
public synchronized String consume() throws InterruptedException {
// Ждём, пока в очереди что-то появится
while (queue.isEmpty()) {
wait(); // Ожидание...
}
String item = queue.poll();
notifyAll(); // Пробуждаю производителей
return item;
}
}
Проблемы с монитором
1. Deadlock (взаимная блокировка)
// ОПАСНО — может привести к deadlock
public class Account {
private int balance;
public synchronized void transferTo(Account other, int amount) {
if (this.balance >= amount) {
this.balance -= amount;
other.deposit(amount); // Попытка захватить другой монитор!
}
}
public synchronized void deposit(int amount) {
this.balance += amount;
}
}
// Если потоки делают одновременно:
// Thread1: account1.transferTo(account2, 100); // Захватил account1
// Thread2: account2.transferTo(account1, 50); // Захватил account2
// => DEADLOCK!
Решение — установить порядок захвата:
public void transferTo(Account other, int amount) {
Account first = this.id < other.id ? this : other;
Account second = this.id < other.id ? other : this;
synchronized(first) {
synchronized(second) {
// Теперь безопасно
}
}
}
2. Performance Issues
// Плохо — синхронизирует весь метод
public synchronized List<String> getAllUsers() {
List<String> result = new ArrayList<>();
for (User user : users) { // Дорогостоящий цикл
result.add(user.getName());
}
return result;
}
// Хорошо — синхронизирует минимум
public List<String> getAllUsersBetter() {
List<String> copy;
synchronized(this) {
copy = new ArrayList<>(users); // Копируем быстро
}
return copy.stream()
.map(User::getName)
.collect(Collectors.toList()); // Обработка без блокировки
}
Современные альтернативы
// Lock interface (более гибко)
Lock lock = new ReentrantLock();
lock.lock();
try {
// Критическая секция
} finally {
lock.unlock();
}
// ReadWriteLock (несколько читателей)
ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock(); // Много потоков могут читать
rwLock.writeLock().lock(); // Только один может писать
// Atomic классы (для примитивов)
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet(); // Thread-safe без синхронизации
// ConcurrentHashMap (синхронизированные коллекции)
Map<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value"); // Thread-safe
Лучшие практики
✓ Используй synchronized для простых случаев ✓ Минимизируй время удержания монитора ✓ Избегай вложенной синхронизации (deadlock risk) ✓ Предпочитай Lock и Atomic для сложных сценариев ✓ Используй ConcurrentHashMap вместо Collections.synchronizedMap ✓ Тестируй многопоточный код на race conditions
Заключение
Монитор — это фундаментальный механизм синхронизации в Java. Каждый объект имеет встроенный монитор для управления доступом к его состоянию. Synchronized методы и блоки гарантируют, что только один поток может выполнять критическую секцию. Для более сложных сценариев используй Lock, ReadWriteLock и Atomic классы.