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

Какие знаешь способы синхронизации обращения к переменной?

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

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

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

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

Способы синхронизации доступа к переменной в Android/Java

В многопоточной среде Android-приложений синхронизация доступа к переменным критически важна для предотвращения состояний гонки (race conditions), обеспечения видимости изменений между потоками и поддержания целостности данных. Вот основные подходы:

1. Ключевое слово synchronized

Наиболее базовый механизм, обеспечивающий взаимное исключение (mutual exclusion).

public class Counter {
    private int count = 0;
    
    // Синхронизированный метод
    public synchronized void increment() {
        count++;
    }
    
    // Синхронизированный блок
    public void decrement() {
        synchronized(this) {
            count--;
        }
    }
}
  • Каждый объект имеет монитор (intrinsic lock)
  • synchronized гарантирует атомарность и видимость изменений (происходит happens-before)
  • Недостатки: возможны взаимные блокировки (deadlocks) и снижение производительности

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

Специализированные классы для атомарных операций без блокировок (lock-free).

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet(); // Атомарная операция
    }
    
    public int get() {
        return count.get();
    }
}
  • AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference
  • Используют CAS-операции (Compare-And-Swap) на уровне процессора
  • Высокая производительность для частых операций в многопоточной среде

3. Вольatile переменные

Гарантирует видимость изменений между потоками, но не атомарность составных операций.

public class VolatileExample {
    private volatile boolean flag = false;
    
    public void toggle() {
        flag = !flag; // НЕ атомарно! Нужна дополнительная синхронизация
    }
    
    public boolean isReady() {
        return flag; // Чтение всегда увидит последнее значение
    }
}
  • Обеспечивает happens-before для операций чтения/записи
  • Подходит для флагов и одноразовых записей
  • НЕ подходит для check-then-act или составных операций

4. Lock интерфейсы

Более гибкая альтернатива synchronized из пакета java.util.concurrent.locks.

import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // Всегда в finally!
        }
    }
}
  • ReentrantLock - аналог synchronized с дополнительными возможностями
  • ReentrantReadWriteLock - разделяет блокировки для чтения и записи
  • Методы tryLock() с таймаутом предотвращают deadlocks

5. ThreadLocal переменные

Каждый поток получает собственную копию переменной.

public class ThreadLocalExample {
    private static final ThreadLocal<SimpleDateFormat> dateFormat =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
    
    public String formatDate(Date date) {
        return dateFormat.get().format(date); // Каждый поток свой экземпляр
    }
}
  • Идеально для per-thread контекста (форматеры, соединения с БД)
  • Избегает синхронизации вообще
  • Важно очищать через remove() для предотвращения утечек памяти

6. Иммутабельные (immutable) объекты

Наиболее эффективный способ - отсутствие необходимости в синхронизации.

public final class ImmutablePoint {
    private final int x;
    private final int y;
    
    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    // Только геттеры, нет сеттеров
    public int getX() { return x; }
    public int getY() { return y; }
}
  • Объект не может измениться после создания
  • Безопасная публикация без синхронизации
  • final поля гарантируют безопасную инициализацию

7. Синхронизированные коллекции

В стандартной библиотеке и пакете java.util.concurrent.

// В Collections
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

// Concurrent collections (предпочтительнее)
ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
CopyOnWriteArrayList<String> copyOnWriteList = new CopyOnWriteArrayList<>();
  • ConcurrentHashMap - сегментированные блокировки
  • CopyOnWriteArrayList - snapshot-копии для итераторов
  • ConcurrentLinkedQueue - неблокирующие очереди

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

  1. Для счетчиков и частых обновлений - используйте атомарные классы
  2. Для флагов и одноразовых изменений - volatile достаточно
  3. Для сложных операций - synchronized или ReentrantLock
  4. Для данных, используемых в UI - синхронизация + публикация в главный поток через Handler или LiveData
  5. Всегда предпочитайте иммутабельные объекты где возможно
  6. Используйте аннотацию @GuardedBy для документирования политики блокировок
import androidx.annotation.GuardedBy;

public class DocumentedExample {
    @GuardedBy("this")
    private int sharedData;
    
    public synchronized void updateData() {
        sharedData++; // Правильно аннотировано
    }
}

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