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

Как работает механизм синхронизации?

2.2 Middle🔥 191 комментариев
#JVM и память#Многопоточность и асинхронность

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

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

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

Механизм синхронизации в Kotlin

Определение

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

Проблема Race Condition

Без синхронизации:

var counter = 0

fun increment() {
    counter++  // ❌ НЕБЕЗОПАСНО!
}

// Если 2 потока вызовут increment() одновременно:
// Thread 1: читает counter (0) → увеличивает → пишет 1
// Thread 2: читает counter (0) → увеличивает → пишет 1
// Результат: counter = 1 вместо 2 (потеря данных!)

1. Synchronized блок

var counter = 0
val lock = Object()

fun increment() {
    synchronized(lock) {
        counter++  // ✅ Потокобезопасно
    }
}

// Thread 1 ждёт, пока Thread 2 закончит
// Результат: counter = 2 (правильно)

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

  1. Поток пытается войти в synchronized блок
  2. Если блок занят другим потоком, ждёт в очереди
  3. Когда блок освобождается, следующий поток входит
  4. Это гарантирует, что только один поток выполняет код одновременно

Визуализация:

Thread 1: ┌─────────────────────┐
          │  synchronized(lock) │
          │  counter++          │ → counter = 1
          └─────────────────────┘
                    ↓
          Блок свободен
                    ↓
Thread 2:         ┌─────────────────────┐
                  │  synchronized(lock) │
                  │  counter++          │ → counter = 2
                  └─────────────────────┘

2. Synchronized функция

class Counter {
    private var value = 0
    
    @Synchronized
    fun increment() {
        value++  // Синхронизирована на this
    }
    
    @Synchronized
    fun getValue(): Int = value
}

// Эквивалентно:
class Counter {
    private var value = 0
    
    fun increment() {
        synchronized(this) {
            value++
        }
    }
}

3. ReentrantLock

import java.util.concurrent.locks.ReentrantLock

class Counter {
    private var value = 0
    private val lock = ReentrantLock()
    
    fun increment() {
        lock.lock()
        try {
            value++
        } finally {
            lock.unlock()  // ВСЕГДА отпустить!
        }
    }
    
    // Или короче:
    fun decrement() {
        lock.withLock {
            value--
        }
    }
}

ReentrantLock vs synchronized:

  • Можно попробовать получить lock: lock.tryLock()
  • Можно установить timeout: lock.tryLock(5, TimeUnit.SECONDS)
  • Более гибкий (например, fair queue)

4. Volatile

volatile var isRunning = false

// Гарантирует:
// - Видимость изменений между потоками
// - НО не гарантирует атомарность операций

fun stop() {
    isRunning = false  // ✅ Все потоки видят это изменение
}

// НЕПРАВИЛЬНО:
volatile var counter = 0
counter++  // ❌ Всё равно race condition!
// (читай + пиши = 2 операции, не атомарная)

5. AtomicInteger / AtomicReference

import java.util.concurrent.atomic.AtomicInteger

val counter = AtomicInteger(0)

fun increment() {
    counter.incrementAndGet()  // ✅ Атомарная операция
}

fun getValue(): Int = counter.get()

// Другие операции:
counter.addAndGet(5)
counter.compareAndSet(0, 1)  // CAS (Compare-And-Swap)
counter.decrementAndGet()

CAS (Compare-And-Swap) механизм:

// "Если значение 0, то установи 1"
val success = counter.compareAndSet(0, 1)
if (success) {
    println("Успешно установили 1")
} else {
    println("Значение изменилось, повторим...")
}

6. Mutex в Coroutines

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val mutex = Mutex()
var counter = 0

suspend fun increment() {
    mutex.withLock {
        counter++
    }
}

// Или вручную:
suspend fun decrement() {
    mutex.lock()
    try {
        counter--
    } finally {
        mutex.unlock()
    }
}

7. Semaphore

import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)  // Максимум 3 потока одновременно

fun processRequest() {
    semaphore.acquire()  // Ждёт, если уже 3 потока
    try {
        // Обработка (максимум 3 одновременно)
        println("Processing...")
    } finally {
        semaphore.release()
    }
}

Сравнительная таблица

СпособПотокобезопасностьПроизводительностьИспользование
synchronized✅ Да❌ НизкаяПростые случаи
ReentrantLock✅ Да✅ ВышеНужна гибкость
volatile⚠️ Только видимость✅✅ ВысокаяФлаги, счётчики
AtomicInteger✅ Да (CAS)✅✅ ВысокаяСчётчики
Mutex✅ Да✅ Для coroutinesКорутины

Пример: Потокобезопасный счётчик

class ThreadSafeCounter {
    // ❌ НЕПРАВИЛЬНО
    private var count = 0
    fun increment() { count++ }
    
    // ✅ ПРАВИЛЬНО 1
    private var count1 = 0
    synchronized fun increment1() { count1++ }
    
    // ✅ ПРАВИЛЬНО 2
    private val count2 = AtomicInteger(0)
    fun increment2() { count2.incrementAndGet() }
    
    // ✅ ПРАВИЛЬНО 3
    private var count3 = 0
    private val lock = ReentrantLock()
    fun increment3() {
        lock.withLock { count3++ }
    }
}

Deadlock: опасность синхронизации

// ❌ DEADLOCK
val lock1 = Object()
val lock2 = Object()

fun process1() {
    synchronized(lock1) {
        Thread.sleep(100)  // Имитация работы
        synchronized(lock2) {  // Ждёт lock2
            println("Process 1")
        }
    }
}

fun process2() {
    synchronized(lock2) {
        Thread.sleep(100)
        synchronized(lock1) {  // Ждёт lock1
            println("Process 2")
        }
    }
}

// Если запустить параллельно: DEADLOCK!

Правило: Избегать Deep Nesting

// ❌ Сложно (deadlock risk)
synchronized(lock1) {
    synchronized(lock2) {
        // код
    }
}

// ✅ Лучше
val combinedLock = Object()
synchronized(combinedLock) {
    // код, защищающий оба ресурса
}

Итог

Механизм синхронизации обеспечивает потокобезопасность за счёт:

  • Блокировки доступа (synchronized, Lock)
  • Атомарных операций (AtomicInteger, CAS)
  • Видимости между потоками (volatile)

Для новых проектов: используй Coroutines + Mutex вместо потоков и synchronized.

Как работает механизм синхронизации? | PrepBro