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

Что будет с программой если не синхронизировать обращение к переменной?

3.0 Senior🔥 232 комментариев
#JVM и память#Многопоточность и асинхронность

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

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

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

Проблема отсутствия синхронизации при обращении к переменной

Если обращение к общей переменной (разделяемому состоянию) в многопоточной среде не синхронизировать, это приведёт к неопределённому поведению программы, известному как race condition (состояние гонки). Основные последствия:

1. Видимость изменений (Visibility Problem)

Изменения переменной, сделанные в одном потоке, могут быть не видны другим потокам из-за:

  • Кеши процессора: Каждый поток работает с локальной копией переменной в кэше ядра.
  • Оптимизации компилятора: Переупорядочивание инструкций для повышения производительности.
// Пример: поток может никогда не увидеть изменение флага
class Example {
    private boolean flag = false; // Не volatile, не synchronized
    
    void thread1() {
        while (!flag) { /* Бесконечный цикл */ }
        System.out.println("Флаг изменён");
    }
    
    void thread2() {
        flag = true; // Изменение может не стать видимым для thread1
    }
}

2. Атомарность операций (Atomicity Problem)

Операции, которые кажутся элементарными, на самом деле состоят из нескольких шагов на уровне процессора:

// Даже инкремент не является атомарной операцией
public class Counter {
    private int count = 0;
    
    public void increment() {
        count++; // НЕАТОМАРНАЯ операция: read -> modify -> write
    }
}

Что происходит при count++ без синхронизации:

  1. Поток A читает значение count (допустим, 5)
  2. Поток B тоже читает значение count (всё ещё 5)
  3. Оба увеличивают своё локальное значение до 6
  4. Оба записывают 6 обратно
  5. Результат: 6 вместо ожидаемых 7

3. Переупорядочивание инструкций (Reordering Problem)

Компилятор и процессор могут менять порядок выполнения инструкций для оптимизации:

// Возможная проблема переупорядочивания
class ReorderingExample {
    private int x = 0;
    private boolean ready = false;
    
    void writer() {
        x = 42;          // (1)
        ready = true;    // (2) - может выполниться ДО (1)
    }
    
    void reader() {
        if (ready) {
            System.out.println(x); // Может вывести 0 вместо 42
        }
    }
}

Конкретные симптомы в программе

Для Android-приложений это особенно критично:

  1. UI глюки и аномалии: Основной поток UI получает некорректные данные из фоновых потоков
  2. Краши приложения: ConcurrentModificationException при работе с коллекциями
  3. Утечки памяти: Несогласованное состояние объектов может препятствовать их корректной очистке
  4. Некорректное отображение данных: Например, в RecyclerView отображаются старые или "смешанные" данные

Пример типичной Android-проблемы:

// ОПАСНО: несинхронизированный доступ к списку из нескольких потоков
class UnsafeViewModel : ViewModel() {
    private val items = mutableListOf<String>()
    
    fun addItem(item: String) {
        // Вызов из фонового потока (например, из ViewModelScope)
        items.add(item)
        
        // Параллельный вызов из UI-потока для обновления RecyclerView
        // МОЖЕТ ВЫЗВАТЬ ConcurrentModificationException
        updateUI(items)
    }
}

Решения для Android-разработки

Правильные подходы:

  1. Использование потокобезопасных коллекций:
val safeList = Collections.synchronizedList(mutableListOf<String>())
// Или
val concurrentList = ConcurrentLinkedQueue<String>()
  1. Применение synchronized:
private val lock = Any()

fun safeIncrement() {
    synchronized(lock) {
        counter++
    }
}
  1. Использование атомарных классов:
private val atomicCounter = AtomicInteger(0)

fun increment() {
    atomicCounter.incrementAndGet()
}
  1. Kotlin Coroutines с потокобезопасными структурами:
class SafeViewModel : ViewModel() {
    private val _items = MutableStateFlow<List<String>>(emptyList())
    val items: StateFlow<List<String>> = _items.asStateFlow()
    
    fun addItem(item: String) {
        viewModelScope.launch {
            _items.update { currentList ->
                currentList + item
            }
        }
    }
}
  1. LiveData (уже потокобезопасна для setValue/postValue):
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data

fun updateData(newValue: String) {
    // Использовать postValue из фоновых потоков
    _data.postValue(newValue)
}

Вывод

Отсутствие синхронизации — одна из самых коварных ошибок в многопоточном программировании, потому что:

  • Проблемы могут проявляться не всегда (зависят от нагрузки, устройства, версии ОС)
  • Ошибки трудно воспроизвести и отладить
  • Могут приводить к непредсказуемым крашам в production

В Android-разработке всегда необходимо явно синхронизировать доступ к изменяемым разделяемым состояниям между потоками, либо использовать потокобезопасные абстракции (LiveData, StateFlow, атомарные классы), которые инкапсулируют эту синхронизацию внутри себя.