Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь варианты синхронизации?
В Java существует несколько подходов для синхронизации доступа к общим ресурсам в многопоточной среде. Рассмотрю основные варианты.
1. Synchronized методы
Самый простой способ синхронизации. Поток должен захватить монитор объекта перед выполнением метода.
public class Counter {
private int count = 0;
// Синхронизация на уровне экземпляра
public synchronized void increment() {
count++;
}
// Синхронизация на уровне класса
public static synchronized void staticMethod() {
// только один поток на класс
}
}
2. Synchronized блоки
Позволяет синхронизировать только часть кода, повышая производительность.
public class BankAccount {
private double balance = 1000;
private Object lock = new Object();
public void withdraw(double amount) {
// Несинхронизированный код
validateAmount(amount);
// Синхронизированная часть
synchronized(lock) {
if(balance >= amount) {
balance -= amount;
}
}
}
// Синхронизация на другом объекте
private Object transferLock = new Object();
public void transfer(BankAccount other, double amount) {
synchronized(transferLock) {
this.withdraw(amount);
other.deposit(amount);
}
}
}
3. Volatile переменные
Гарантирует видимость изменений между потоками, но не атомарность операций.
public class VolatileExample {
private volatile boolean flag = false; // всегда читаем/пишем из памяти
private volatile int counter = 0;
public void setFlag(boolean value) {
flag = value; // немедленно видно всем потокам
}
public boolean getFlag() {
return flag; // читаем актуальное значение
}
// Внимание: counter++ не атомарна, даже с volatile!
// Нужно использовать AtomicInteger или synchronized
}
4. Atomic классы (java.util.concurrent.atomic)
Обеспечивают атомарные операции без использования блокировок (lock-free).
public class AtomicExample {
private AtomicInteger counter = new AtomicInteger(0);
private AtomicBoolean running = new AtomicBoolean(true);
private AtomicReference<String> name = new AtomicReference<>("initial");
public void increment() {
counter.incrementAndGet(); // ++counter
counter.addAndGet(5); // counter += 5
counter.getAndSet(10); // oldValue = counter; counter = 10
counter.compareAndSet(10, 20); // if(counter == 10) counter = 20
}
public boolean isRunning() {
return running.get();
}
public void stop() {
running.set(false);
}
}
5. ReentrantLock (явная блокировка)
Больше возможностей, чем synchronized: попытка захвата, попытка с таймаутом, справедливость.
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
// Базовое использование
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock(); // важно в finally!
}
}
// Попытка захватить с таймаутом
public boolean tryIncrement() {
try {
if(lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
counter++;
return true;
} finally {
lock.unlock();
}
}
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
return false;
}
// Справедливая блокировка (FIFO)
private final ReentrantLock fairLock = new ReentrantLock(true);
}
6. ReadWriteLock
Позволяет множество читателей одновременно, но только одного писателя.
public class CacheWithReadWriteLock {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public String get(String key) {
lock.readLock().lock(); // Множество потоков могут читать
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(String key, String value) {
lock.writeLock().lock(); // Только один поток пишет
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
7. Semaphore
Ограничивает количество потоков, имеющих доступ к ресурсу.
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore(3); // 3 потока одновременно
public void accessResource() {
try {
semaphore.acquire(); // Уменьшить счётчик
// Критическая секция (максимум 3 потока)
doWork();
} finally {
semaphore.release(); // Увеличить счётчик
}
}
}
8. CountDownLatch
Позволяет потокам ждать, пока другие потоки выполнят определённые действия.
public class CountDownLatchExample {
public void waitForMultipleThreads() {
CountDownLatch latch = new CountDownLatch(3);
for(int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Work started");
// Выполнить работу
System.out.println("Work finished");
latch.countDown(); // Уменьшить счётчик
}).start();
}
// Главный поток ждёт
try {
latch.await(); // Ждёт, пока счётчик обнулится
System.out.println("All threads finished");
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
9. CyclicBarrier
Позволяет группе потоков дождаться друг друга в определённой точке.
public class CyclicBarrierExample {
public void waitForAllThreads() {
CyclicBarrier barrier = new CyclicBarrier(3, () ->
System.out.println("All threads reached barrier"));
for(int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " waiting");
barrier.await(); // Ждёт других потоков
System.out.println(Thread.currentThread().getName() + " continuing");
} catch(Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
10. Synchronized Collections vs Concurrent Collections
// Synchronized (старый подход)
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// Concurrent (современный подход - лучше!)
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
// ConcurrentHashMap использует сегментную блокировку (Bucket locking)
// вместо блокировки всего объекта, что даёт лучшую производительность
List<String> list = new CopyOnWriteArrayList<>(); // Для частых чтений
Сравнение подходов
| Подход | Производительность | Сложность | Гибкость |
|---|---|---|---|
| Synchronized | Низкая (грубая блокировка) | Простая | Низкая |
| Volatile | Высокая (но не для ++i) | Простая | Низкая |
| AtomicXX | Высокая (lock-free) | Средняя | Низкая |
| ReentrantLock | Средняя | Сложная | Высокая |
| ReadWriteLock | Высокая (много читателей) | Сложная | Высокая |
| Concurrent Collections | Высокая | Простая | Средняя |
Лучшие практики
- Используй Concurrent Collections по умолчанию
- Для простых счётчиков - AtomicInteger
- Для критических секций малого размера - synchronized
- Для сложной синхронизации - ReentrantLock
- Избегай synchronized методы на public объектах (делай блоки)
- Всегда используй try-finally для освобождения ресурсов
- Предпочитай неизменяемость (immutable objects) синхронизации