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

Какие знаешь способы решения обращения к одному полю несколькими потоками?

1.6 Junior🔥 171 комментариев
#Многопоточность

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

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

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

Синхронизация доступа к полям при многопоточности

Когда несколько потоков обращаются к одному полю одновременно, возникают race conditions, data races и другие проблемы многопоточности. Расскажу о проверенных способах решения.

1. Synchronized блоки и методы

Первый встроенный механизм синхронизации в Java.

public class Counter {
    private int count = 0;
    
    // Синхронизация метода
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// Или синхронизация блока (более гибко)
public class CounterWithBlock {
    private int count = 0;
    private final Object lock = new Object(); // explicit lock object
    
    public void increment() {
        synchronized (lock) {
            count++; // защищено от race condition
        }
    }
    
    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

// ❌ Неправильно — разные блокировки
public class BadCounter {
    private int count = 0;
    
    public void increment() {
        synchronized (new Object()) { // новая блокировка каждый раз!
            count++;
        }
    }
}

2. Volatile

Для простых переменных, когда нужна видимость между потоками.**

public class Flag {
    private volatile boolean flag = false; // гарантирует видимость
    
    public void set(boolean value) {
        flag = value; // пишется в main memory
    }
    
    public boolean get() {
        return flag; // читается из main memory
    }
}

// Использование в сигнализации выхода
public class WorkerThread extends Thread {
    private volatile boolean shouldStop = false;
    
    @Override
    public void run() {
        while (!shouldStop) {
            doWork();
        }
    }
    
    public void stopWorker() {
        shouldStop = true; // другой поток может остановить
    }
}

Важно: volatile работает только для видимости, не для атомарности!

// ❌ Неправильно
private volatile int counter = 0;
public void increment() {
    counter++; // это НЕ атомарная операция!
}

// ✅ Правильно для просто чтения/записи значения
private volatile long timestamp = System.currentTimeMillis();
public void updateTimestamp() {
    timestamp = System.currentTimeMillis();
}

3. AtomicInteger, AtomicLong, AtomicReference

Атомарные операции без явной синхронизации.

public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet(); // атомарная операция
    }
    
    public int getAndIncrement() {
        return count.getAndIncrement(); // вернёт старое значение
    }
    
    public int get() {
        return count.get();
    }
    
    // Условное обновление
    public void compareAndSet(int expected, int newValue) {
        count.compareAndSet(expected, newValue);
    }
}

// AtomicReference для объектов
public class UserCache {
    private final AtomicReference<User> cachedUser = 
        new AtomicReference<>();
    
    public void setUser(User user) {
        cachedUser.set(user); // атомарная запись
    }
    
    public User getUser() {
        return cachedUser.get(); // атомарное чтение
    }
}

4. ReentrantLock

Более гибкая альтернатива synchronized.

public class LockBasedCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // ВСЕГДА в finally!
        }
    }
    
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
    
    // Попытка получить блокировку с timeout
    public boolean tryIncrement(long timeout, TimeUnit unit) {
        try {
            if (lock.tryLock(timeout, unit)) {
                try {
                    count++;
                    return true;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }
}

5. ReadWriteLock

Много читателей, мало писателей.

public class CacheWithReadWriteLock<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public V get(K key) {
        lock.readLock().lock(); // много потоков могут читать одновременно
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(K key, V value) {
        lock.writeLock().lock(); // только один писатель
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public boolean containsKey(K key) {
        lock.readLock().lock();
        try {
            return cache.containsKey(key);
        } finally {
            lock.readLock().unlock();
        }
    }
}

6. Semaphore

Контроль количества потоков, получающих доступ.

public class RateLimitedDatabase {
    private final Semaphore semaphore = new Semaphore(10); // только 10 потоков
    
    public Data queryDatabase(String sql) throws InterruptedException {
        semaphore.acquire(); // ждём доступного слота
        try {
            return executeQuery(sql);
        } finally {
            semaphore.release(); // освобождаем слот
        }
    }
}

// Использование
ExecutorService executor = Executors.newFixedThreadPool(20);
for (int i = 0; i < 100; i++) {
    executor.submit(() -> {
        Data result = rateLimiter.queryDatabase("SELECT ...");
        processResult(result);
    });
}

7. Phaser

Синхронизация фаз между потоками.

public class DataProcessingPipeline {
    private final Phaser phaser = new Phaser(3); // 3 потока
    
    public void processPhase1(String threadName) throws InterruptedException {
        System.out.println(threadName + " - фаза 1");
        phaser.arriveAndAwaitAdvance(); // ждём других
    }
    
    public void processPhase2(String threadName) throws InterruptedException {
        System.out.println(threadName + " - фаза 2");
        phaser.arriveAndAwaitAdvance();
    }
}

// Использование
DataProcessingPipeline pipeline = new DataProcessingPipeline();

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        String name = Thread.currentThread().getName();
        try {
            pipeline.processPhase1(name);
            pipeline.processPhase2(name);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
}

8. CyclicBarrier

Встреча фиксированного количества потоков.

public class ParallelSort {
    private final int[] data;
    private final int threadCount = 4;
    private final CyclicBarrier barrier = new CyclicBarrier(threadCount);
    
    public void parallelSort() throws BrokenBarrierException, InterruptedException {
        int chunkSize = data.length / threadCount;
        
        for (int t = 0; t < threadCount; t++) {
            final int threadId = t;
            new Thread(() -> {
                int start = threadId * chunkSize;
                int end = (threadId == threadCount - 1) ? data.length : start + chunkSize;
                
                // Сортируем свой кусок
                Arrays.sort(data, start, end);
                
                try {
                    barrier.await(); // ждём остальных потоков
                } catch (InterruptedException | BrokenBarrierException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

9. StampedLock

Оптимизированная версия ReadWriteLock.

public class OptimizedCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final StampedLock lock = new StampedLock();
    
    public V get(K key) {
        // Optimistic read — очень быстро
        long stamp = lock.tryOptimisticRead();
        V result = cache.get(key);
        
        // Проверяем, что данные не поменялись
        if (!lock.validate(stamp)) {
            // Если поменялись, читаем с полной блокировкой
            stamp = lock.readLock();
            try {
                result = cache.get(key);
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return result;
    }
    
    public void put(K key, V value) {
        long stamp = lock.writeLock();
        try {
            cache.put(key, value);
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

10. Thread-Safe Collections

Используем готовые thread-safe коллекции.

// ConcurrentHashMap для конкурентного доступа
public class UserStore {
    private final ConcurrentHashMap<UUID, User> users = new ConcurrentHashMap<>();
    
    public void addUser(UUID id, User user) {
        users.put(id, user); // потокобезопасно
    }
    
    public User getUser(UUID id) {
        return users.get(id); // потокобезопасно
    }
    
    // Atomic compute операции
    public void incrementUserScore(UUID userId, int amount) {
        users.computeIfPresent(userId, (id, user) -> {
            user.incrementScore(amount);
            return user;
        });
    }
}

// CopyOnWriteArrayList для часто читаемых, редко пишущихся списков
public class EventListeners {
    private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
    
    public void addListener(EventListener listener) {
        listeners.add(listener);
    }
    
    public void notifyListeners(Event event) {
        for (EventListener listener : listeners) {
            listener.onEvent(event); // безопасно даже если изменяется список
        }
    }
}

Выбор правильного инструмента

ЗадачаРешениеПроизводительность
Простой счётчикAtomicIntegerОтличная
Boolean флагvolatileОтличная
Одна блокировкаsynchronizedХорошая
Flexible блокировкаReentrantLockХорошая
Много читателейReadWriteLockХорошая
Контроль доступаSemaphoreХорошая
Синхронизация фазCyclicBarrier/PhaserХорошая
Оптимистичное чтениеStampedLockОтличная
КоллекцииConcurrent*Хорошая

Лучшие практики

// ✅ Правильно
private final Object lock = new Object(); // final, immutable

private volatile boolean flag = false; // только для видимости

private final AtomicInteger count = new AtomicInteger(); // для атомарности

private final ReentrantLock lock = new ReentrantLock();
locked.lock();
try {
    // работа
} finally {
    lock.unlock();
}

// ❌ Избегай
public synchronized void method() {} // синхронизируем класс вместо данных

private int count; // ничего не защищено
count++; // не атомарно

private Object lock = new Object(); // может быть переназначен

Заключение

Выбор механизма синхронизации зависит от:

  • Сложности логики
  • Производительности
  • Частоты конфликтов
  • Типа доступа (чтение/запись)

Для 90% случаев достаточно synchronized или Atomic классов. Более сложные механизмы нужны для специализированных сценариев.