Для чего используют ключевое слово synchronized?android-25

Ключевое слово synchronized используется для контроля доступа нескольких потоков к общим ресурсам, предотвращая race conditions (состояния гонки) и обеспечивая потокобезопасность.

Основные принципы работы

  1. Монитор объекта

    • Каждый объект в Java/Kotlin имеет связанный с ним монитор (lock)
    • Когда поток входит в synchronized блок, он захватывает этот монитор
    • Другие потоки блокируются до освобождения монитора
  2. Два способа использования:

    Синхронизированный метод:

    @Synchronized
    fun updateCounter() {
        counter++ // Потокобезопасная операция
    }
    

    Синхронизированный блок:

    fun updateUser(user: User) {
        synchronized(this) { // Явное указание объекта блокировки
            // Критическая секция
            user.balance += amount
        }
    }
    

Типичные сценарии использования в Android

  1. Изменение общих данных:

    class Repository {
        private val cache = mutableMapOf<String, Data>()
        private val lock = Object() // Специальный объект для блокировки
    
        fun putData(key: String, value: Data) {
            synchronized(lock) {
                cache[key] = value
            }
        }
    }
    
  2. Работа с Singleton (ленивая инициализация):

    class Singleton private constructor() {
        companion object {
            @Volatile private var instance: Singleton? = null
    
            fun getInstance(): Singleton {
                return instance ?: synchronized(this) {
                    instance ?: Singleton().also { instance = it }
                }
            }
        }
    }
    

Особенности производительности

  1. Накладные расходы:

    • Каждый synchronized блок создает нагрузку
    • В среднем в 5-6 раз медленнее обычного кода
  2. Оптимизации:

    • Используйте минимально необходимую область синхронизации
    • Для чтения предпочтительнее @Volatile или Atomic классы
    • Рассмотрите ReentrantLock для сложных сценариев

Альтернативы в Kotlin

  1. Mutex (из kotlinx.coroutines):

    private val mutex = Mutex()
    
    suspend fun safeUpdate() {
        mutex.withLock {
            // Критическая секция
        }
    }
    
  2. Атомарные переменные:

    private val atomicCounter = AtomicInteger(0)
    
    fun increment() {
        atomicCounter.incrementAndGet() // Без блокировок
    }
    

Опасные ситуации

  1. Взаимная блокировка (Deadlock):

    // Поток 1:
    synchronized(lockA) {
        synchronized(lockB) { ... }
    }
    
    // Поток 2:
    synchronized(lockB) {
        synchronized(lockA) { ... } // Возможен deadlock
    }
    
  2. Избыточная синхронизация:

    • Синхронизация неизменяемых объектов
    • Синхронизация методов, которые всегда вызываются из одного потока

Резюмируем:

synchronized — это мощный инструмент для обеспечения потокобезопасности, но он требует аккуратного использования. В современных Android-приложениях предпочтение часто отдается корутинам с Mutex или потокобезопасным коллекциям из пакета java.util.concurrent.