Для чего нужен synchronized?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего нужен synchronized
synchronized — это ключевое слово в Kotlin/Java для синхронизации доступа к ресурсам в многопоточной среде. Оно обеспечивает, чтобы только один поток одновременно мог выполнять защищённый код, предотвращая состояние гонки (race condition) и гарантируя thread-safety.
Проблема без synchronized
Представим счётчик, используемый несколькими потоками:
class Counter {
var count = 0
fun increment() {
count++ // опасно без синхронизации!
}
}
val counter = Counter()
repeat(5) {
thread {
repeat(1000) {
counter.increment()
}
}
}
Thread.sleep(2000)
println(counter.count) // Результат: не 5000, например 3847!
Почему результат неправильный?
Операция count++ состоит из трёх шагов:
- Прочитать текущее значение count
- Увеличить его на 1
- Записать новое значение
Если два потока выполняют это одновременно:
Поток 1: читает count=5
Поток 2: читает count=5 (!)
Поток 1: пишет count=6
Поток 2: пишет count=6 (вместо 7!)
Инкремент потока 2 потерян, произойдёт потеря данных.
synchronized для методов
Найпростой способ — синхронизировать весь метод:
class Counter {
var count = 0
synchronized fun increment() {
count++ // теперь безопасно
}
}
Теперь только один поток может одновременно вызвать increment(). Остальные будут ждать освобождения блокировки.
synchronized блок
Можно синхронизировать только часть кода:
class BankAccount {
private var balance = 1000.0
private val lock = Any()
fun withdraw(amount: Double) {
// Это может быть не синхронизировано
val fee = calculateFee(amount)
// Только критическая секция синхронизирована
synchronized(lock) {
if (balance >= amount) {
balance -= amount
recordTransaction(amount)
}
}
}
}
Это более эффективно, так как только существенная операция защищена.
Синхронизация на объекте
val monitor = Any() // объект для синхронизации
synchronized(monitor) {
// Код выполняется с блокировкой на monitor
// Другие потоки будут ждать
}
synchronized(monitor) {
// Повторное входит в тот же monitor
// (реентрантная блокировка)
}
Гарантии synchronized
1. Взаимное исключение
Только один поток может быть в synchronized блоке одновременно:
val data = mutableListOf<Int>()
val lock = Any()
thread {
synchronized(lock) {
data.add(1)
Thread.sleep(1000) // другой поток подождёт
}
}
thread {
synchronized(lock) {
data.add(2) // это выполнится ПОСЛЕ первого потока
}
}
2. Видимость памяти (Memory Visibility)
synchronized гарантирует, что изменения, сделанные одним потоком, видны другим:
class Flag {
var done = false
fun markDone() {
synchronized(this) {
done = true // видимо для других потоков
}
}
fun isDone(): Boolean = synchronized(this) {
done // видно последнее значение
}
}
Без synchronized второй поток может читать старое значение из кеша.
Практический пример: потокобезопасный Singleton
class Singleton private constructor() {
companion object {
private var instance: Singleton? = null
fun getInstance(): Singleton {
if (instance == null) {
synchronized(this) {
if (instance == null) { // Double-Checked Locking
instance = Singleton()
}
}
}
return instance!!
}
}
}
Недостатки synchronized
- Производительность: Блокировка замедляет выполнение
- Deadlock: Неправильное использование может привести к зависаниям
- Контроль: Нет тайм-аутов или попыток неблокирующего захвата
Альтернативы
1. AtomicInteger (для простых операций)
val counter = AtomicInteger(0)
repeat(5) {
thread {
repeat(1000) {
counter.incrementAndGet() // атомарная операция
}
}
}
Thread.sleep(2000)
println(counter.get()) // 5000 - правильно!
2. ReentrantLock (больше контроля)
val lock = ReentrantLock()
var count = 0
lock.lock()
try {
count++
} finally {
lock.unlock()
}
// С тайм-аутом
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
count++
} finally {
lock.unlock()
}
}
3. Coroutines с Mutex (асинхронный вариант)
val mutex = Mutex()
var count = 0
suspend fun incrementAsync() {
mutex.withLock {
count++ // не блокирует поток
}
}
Правило выбора
| Ситуация | Решение |
|---|---|
| Простой счётчик | AtomicInteger |
| Защита нескольких переменных | synchronized блок |
| Нужен тайм-аут | ReentrantLock |
| Асинхронный код (suspend) | Mutex из coroutines |
| Несложная логика | Volatile |
Итого
synchronized — это фундаментальный инструмент для потокобезопасности:
- Предотвращает race conditions
- Гарантирует видимость памяти
- Легко использовать для начинающих
Но в современном коде Kotlin предпочитают coroutines и Mutex, так как они более гибкие и лучше масштабируются.