← Назад к вопросам

К каким конструкциям языка можно применить synchronized

2.0 Middle🔥 121 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

synchronized — это ключевое слово в Java для синхронизации доступа к ресурсам в многопоточной среде. Его можно применить к разным конструкциям языка, и каждая имеет свои особенности.

1. Synchronized методы (методы класса)

Самый простой способ синхронизации.

public class BankAccount {
    private int balance = 0;
    
    // Синхронизованный метод
    public synchronized void deposit(int amount) {
        balance += amount;
        // Только один поток может выполнять этот метод одновременно
    }
    
    public synchronized int getBalance() {
        return balance;
    }
}

Как работает:

  • Блокировка приобретается на объект (this для обычных методов)
  • Другие потоки ждут, пока первый поток завершит метод
  • Освобождение блокировки происходит автоматически при выходе из метода

Проблемы:

  • Весь метод синхронизован, даже если нужна блокировка только для части кода
  • Может быть узкое место производительности

2. Synchronized блоки (блоки кода)

Более гибкий способ — синхронизировать только необходимую часть кода.

public class BankAccount {
    private int balance = 0;
    private final Object lock = new Object(); // объект для блокировки
    
    public void deposit(int amount) {
        // код ДО блокировки выполняется без синхронизации
        validateAmount(amount);
        
        // синхронизованный блок
        synchronized (lock) {
            balance += amount; // защищенный код
        }
        
        // код ПОСЛЕ блокировки
        notifyListener();
    }
    
    private void validateAmount(int amount) {
        // не нуждается в синхронизации
    }
}

Преимущества:

  • Синхронизируется только критическая секция
  • Лучшая производительность
  • Меньше контенции между потоками

Объект блокировки:

// Можно использовать any объект
synchronized (this) { ... }
synchronized (lock) { ... }
synchronized (SomeClass.class) { ... }

3. Synchronized статические методы

Для синхронизации между потоками, работающими с классом (а не экземпляром).

public class Counter {
    private static int count = 0;
    
    // Синхронизированный статический метод
    // Блокировка на объект Counter.class
    public static synchronized void increment() {
        count++;
    }
    
    public static synchronized int getCount() {
        return count;
    }
}

Важно: Блокировка на Counter.class, не на конкретный экземпляр. Все потоки (независимо от того, с каким объектом они работают) будут ждать друг друга.

Эквивалент:

public static void increment() {
    synchronized (Counter.class) {
        count++;
    }
}

4. Synchronized на this (синхронизация методов через this)

Эквивалент synchronized методов.

public class BankAccount {
    private int balance = 0;
    
    // Эти два способа эквивалентны:
    
    // Способ 1: synchronized метод
    public synchronized void deposit1(int amount) {
        balance += amount;
    }
    
    // Способ 2: synchronized блок на this
    public void deposit2(int amount) {
        synchronized (this) {
            balance += amount;
        }
    }
}

5. Synchronized на объект-блокировку

Найбольшая гибкость.

public class BankAccount {
    private int balance = 0;
    private List<String> transactions = new ArrayList<>();
    private final Object balanceLock = new Object();
    private final Object transactionLock = new Object();
    
    public void deposit(int amount) {
        // Блокировка только на balance
        synchronized (balanceLock) {
            balance += amount;
        }
        
        // Отдельная блокировка на transactions
        synchronized (transactionLock) {
            transactions.add("Deposit: " + amount);
        }
    }
}

Преимущество: Разные блокировки для разных данных = меньше контенции.

6. Synchronized на объект класса (Class object)

Для глобальной синхронизации.

public class LoggingService {
    private static int requestCounter = 0;
    
    public static void logRequest() {
        synchronized (LoggingService.class) {
            requestCounter++;
            System.out.println("Request #" + requestCounter);
        }
    }
}

Практический пример: правильная синхронизация

public class ThreadSafeCounter {
    private int count = 0;
    private final Object lock = new Object();
    
    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
    
    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

// Использование
public class Main {
    public static void main(String[] args) throws InterruptedException {
        ThreadSafeCounter counter = new ThreadSafeCounter();
        
        // 10 потоков инкрементируют счетчик
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println(counter.getCount()); // 10000 (правильно!)
    }
}

Проблемы и современные альтернативы

Проблема 1: Dead lock

// ОПАСНО: может привести к deadlock
public class Account {
    public synchronized void transferTo(Account other, int amount) {
        this.balance -= amount;
        other.deposit(amount); // может быть синхронизирован!
    }
}

// Поток 1: acc1.transferTo(acc2, 100) — блокирует acc1, ждет acc2
// Поток 2: acc2.transferTo(acc1, 100) — блокирует acc2, ждет acc1
// DEADLOCK!

Решение: Lock'и из java.util.concurrent.locks

public class BankAccount {
    private int balance = 0;
    private final Lock lock = new ReentrantLock();
    
    public void deposit(int amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }
}

Проблема 2: Необходимость явного управления монитором

Решение: java.util.concurrent классы

import java.util.concurrent.*;

// Вместо synchronized
public class SafeCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet(); // атомарная операция, no locks
    }
    
    public int getCount() {
        return count.get();
    }
}

// Или Semaphore
public class RateLimiter {
    private Semaphore semaphore = new Semaphore(10); // 10 одновременных потоков
    
    public void execute(Runnable task) throws InterruptedException {
        semaphore.acquire();
        try {
            task.run();
        } finally {
            semaphore.release();
        }
    }
}

Сравнение: synchronized vs современные инструменты

АспектsynchronizedLockAtomicInteger
ПростотаПростаяСложнееЗависит
ПроизводительностьХорошаяЛучшеЛучшая (CAS)
ГибкостьОграниченаВысокаяДля простых операций
FairnessНетМожно настроитьНе применимо
DeadlockВозможенВозможенНет

Best Practices

1. Минимизируйте synchronized блоки

// ПЛОХО: весь метод синхронизован
public synchronized void processUser(User user) {
    validateUser(user); // не нуждается в синхронизации
    saveUser(user); // долгая операция — I/O
    notifyListener(user); // не нуждается в синхронизации
}

// ХОРОШО: синхронизируем только необходимое
public void processUser(User user) {
    validateUser(user);
    synchronized (lock) {
        saveUser(user);
    }
    notifyListener(user);
}

2. Используйте правильный объект блокировки

// ПЛОХО: использование this может быть проблемой
public class MyClass {
    public synchronized void method1() { ... }
    public synchronized void method2() { ... }
}

// ХОРОШО: использование приватного объекта блокировки
public class MyClass {
    private final Object lock = new Object();
    
    public void method1() {
        synchronized (lock) { ... }
    }
    
    public void method2() {
        synchronized (lock) { ... }
    }
}

3. Предпочитайте java.util.concurrent для новых проектов

// Вместо synchronized используйте:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
AtomicInteger counter = new AtomicInteger();

Вывод

synchronized можно применить к:

  1. Методам (обычным и статическим)
  2. Блокам кода (с явным объектом блокировки)
  3. this (эквивалент synchronized методов)
  4. Объектам блокировки (гибкое управление)
  5. Class объектам (для статической синхронизации)

Современные приложения часто используют java.util.concurrent вместо synchronized, так как это безопаснее и производительнее.

К каким конструкциям языка можно применить synchronized | PrepBro