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

Что происходит если несколько потоков пытаются работать с одним объектом

2.0 Middle🔥 161 комментариев
#Многопоточность и асинхронность

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Механизмы и последствия конкурентного доступа к общему объекту

Когда несколько потоков пытаются работать с одним объектом одновременно, возникает ситуация конкурентного доступа (concurrent access) или состояние гонки (race condition). Это фундаментальная проблема многопоточного программирования, которая может привести к непредсказуемому и некорректному поведению приложения.

Основные проблемы при конкурентном доступе

  1. Нарушение атомарности операций Большинство операций в Java/Kotlin не являются атомарными (невыполнимыми за один неделимый шаг). Например, инкремент переменной count++ на самом деле состоит из трёх операций:

    // Псевдокомпиляция операции count++
    int tmp = count;  // 1. Чтение значения
    tmp = tmp + 1;    // 2. Увеличение
    count = tmp;      //第三届. Запись значения
    

    Если два потока выполняют эту операцию одновременно, возможна потеря обновления.

  2. Видимость изменений (Memory Visibility) Без синхронизации изменения, сделанные одним потоком, могут быть не видны другим потокам из- за особенностей работы кэшей процессора и оптимизаций компилятора/JVM.

  3. Нарушение согласованности состояния Если объект имеет несколько связанных полей, один поток может увидеть объект в промежуточном, несогласованном состоянии.

Пример проблемы на Kotlin

class Counter {
    var count = 0
    
    fun increment() {
        count++  // Небезопасная операция!
    }
}

// В разных потоках
fun main() {
    val counter = Counter()
    
    val thread1 = Thread { repeat(1000) { counter.increment() } }
    val thread2 = Thread { repeat(1000) { counter.increment() } }
    
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    
    println("Result: ${counter.count}") // Может быть меньше 2000!
}

Решения для безопасного конкурентного доступа

1. Синхронизация (synchronized)

class SafeCounter {
    private var count = 0
    
    @Synchronized
    fun increment() {
        count++
    }
    
    // Или с явным synchronized блоком
    fun decrement() {
        synchronized(this) {
            count--
        }
    }
}

2. Атомарные классы (java.util.concurrent.atomic)

import java.util.concurrent.atomic.AtomicInteger

class AtomicCounter {
    private val count = AtomicInteger(0)
    
    fun increment() {
        count.incrementAndGet()
    }
    
    fun getValue(): Int = count.get()
}

3. Иммутабельность (Immutable Objects)

Создание неизменяемых объектов полностью устраняет проблему:

data class ImmutableData(val value: Int, val name: String)
// Объект после создания нельзя изменить

4. Потокобезопасные коллекции

// Вместо обычного ArrayList
val safeList = Collections.synchronizedList(mutableListOf<String>())
// Или из пакета concurrent
val concurrentMap = ConcurrentHashMap<String, String>()

5. Использование локов из java.util.concurrent.locks

import java.util.concurrent.locks.ReentrantLock

class LockCounter {
    private var count = 0
    private val lock = ReentrantLock()
    
    fun increment() {
        lock.lock()
        try {
            count++
        } finally {
            lock.unlock()
        }
    }
}

Особенности в Android разработке

В Android дополнительно существуют:

  1. @MainThread/@WorkerThread аннотации для контроля потока выполнения
  2. Handler/Looper механизм для работы с основным потоком
  3. LiveData/Flow с их built-in потокобезопасностью
  4. Coroutines с мьютексами в Kotlin:
    import kotlinx.coroutines.sync.Mutex
    import kotlinx.coroutines.sync.withLock
    
    class CoroutineCounter {
        private var count = 0
        private val mutex = Mutex()
        
        suspend fun increment() {
            mutex.withLock {
                count++
            }
        }
    }
    

Рекомендации по проектированию

  1. Минимизируйте общее состояние между потоками
  2. Используйте принцип "либо потокобезопасно, либо не делитесь"
  3. Документируйте потокобезопасность классов явно
  4. Предпочитайте иммутабельность изменяемым объектам
  5. Используйте высокоуровневые конструкции из java.util.concurrent вместо ручной синхронизации

Конкурентный доступ требует внимательного проектирования, так как ошибки могут проявляться только при определенных условиях нагрузки и быть трудно воспроизводимыми. Тестирование таких сценариев должно включать стресс}

Что происходит если несколько потоков пытаются работать с одним объектом | PrepBro