Логотип YeaHub

База вопросов

Собеседования

Тренажёр

База ресурсов

Обучение

Навыки

Войти

Выбери, каким будет IT завтра — вместе c нами!

YeaHub — это полностью открытый проект, призванный объединить и улучшить IT-сферу. Наш исходный код доступен для просмотра на GitHub. Дизайн проекта также открыт для ознакомления в Figma.

© 2026 YeaHub

Документы

Медиа

Назад
Вопрос про Java: thread safety, atomic operations, synchronization, concurrent counter, race condition

Как реализовать потокобезопасный счетчик в многопоточном приложении?

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

Короткий ответ

Потокобезопасный счетчик гарантирует, что его значение будет корректно увеличиваться или уменьшаться при одновременном доступе из нескольких потоков. Простейший способ — использовать примитивы синхронизации, такие как мьютексы (mutex), которые блокируют доступ к счетчику для всех потоков, кроме одного. В языках, поддерживающих атомарные операции, можно использовать специальные типы (например, std::atomic в C++ или AtomicInteger в Java), которые обеспечивают корректное изменение значения за одну аппаратную инструкцию. Это эффективнее мьютексов, так как не требует блокировок на уровне операционной системы.

Длинный ответ

Потокобезопасный счетчик — это структура данных, значение которой можно изменять (например, инкрементировать) из нескольких потоков одновременно без риска получить некорректный результат из-за состояния гонки (race condition). Состояние гонки возникает, когда два или более потока читают и пишут в одну переменную без синхронизации, и итоговое значение зависит от порядка выполнения инструкций, что ведет к ошибкам.

Основные подходы к реализации

Существует два основных подхода для обеспечения потокобезопасности счетчика:

  • Использование мьютексов (блокировок): перед изменением счетчика поток захватывает блокировку, что гарантирует эксклюзивный доступ. Это универсальный, но не самый быстрый метод, так как потоки могут простаивать в ожидании.
  • Использование атомарных операций: многие процессоры и языки программирования предоставляют атомарные (неделимые) инструкции для операций типа инкремента. Они выполняются за один такт и не требуют блокировок, что значительно повышает производительность в высоконагруженных сценариях.

Примеры кода

Пример на C++ с использованием std::mutex:

#include 
#include 
#include 
#include 

class ThreadSafeCounter {
private:
    int value = 0;
    std::mutex mtx;
public:
    void increment() {
        std::lock_guard lock(mtx);
        ++value;
    }
    int get() {
        std::lock_guard lock(mtx);
        return value;
    }
};

int main() {
    ThreadSafeCounter counter;
    std::vector threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back([&counter]() {
            for (int j = 0; j < 1000; ++j) {
                counter.increment();
            }
        });
    }
    for (auto& t : threads) t.join();
    std::cout << counter.get() << std::endl; // Всегда 10000
    return 0;
}

Пример на Java с использованием AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AtomicCounterExample {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger counter = new AtomicInteger(0);
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.incrementAndGet(); // Атомарный инкремент
                }
            });
        }
        executor.shutdown();
        while (!executor.isTerminated()) { }
        
        System.out.println(counter.get()); // Всегда 10000
    }
}

Где применяется

Потокобезопасные счетчики используются везде, где требуется собирать статистику в многопоточных системах: подсчет количества запросов в веб-сервере, отслеживание числа активных соединений, реализация ограничителей скорости (rate limiters), счетчики ссылок в умных указателях, и в любых структурах данных, где значение разделяется между потоками.

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

Уровень

  • Рейтинг:

    4

  • Сложность:

    6

Навыки

  • Java

    Java

  • C++

    C++

Ключевые слова

#thread safety

#atomic operations

#synchronization

#concurrent counter

#race condition

Подпишись на Java Developer в телеграм