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

Как реализуешь счетчик внутри метода по подсчету вхождений в этот метод, находящийся внутри бина в Spring без concurrent?

1.7 Middle🔥 101 комментариев
#Spring Framework#Многопоточность

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

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

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

# Как реализуешь счетчик внутри метода по подсчету вхождений в этот метод, находящийся внутри бина в Spring без concurrent

Это интересный вопрос о отслеживании количества вызовов метода в Spring бине без использования concurrent классов.

1. Простой способ: переменная в классе + synchronized

@Service
public class UserService {
    private int callCount = 0;  // Счетчик
    
    public synchronized void processUser(Long userId) {
        callCount++;  // Увеличиваем при каждом вызове
        
        // Бизнес логика
        User user = findUser(userId);
        // ...
        
        System.out.println("Method called " + callCount + " times");
    }
    
    public int getCallCount() {
        return callCount;
    }
}

Особенности:

  • callCount++ — не потокобезопасно само по себе
  • synchronized блокирует весь метод
  • Работает, но медленнее при большой нагрузке

2. Способ 2: synchronized только на счетчик

@Service
public class UserService {
    private int callCount = 0;
    private Object lockObject = new Object();
    
    public void processUser(Long userId) {
        // Только счетчик заблокирован
        synchronized (lockObject) {
            callCount++;
        }
        
        // Основная логика без блокировки
        User user = findUser(userId);
        // ... долгие операции
    }
    
    public int getCallCount() {
        synchronized (lockObject) {
            return callCount;
        }
    }
}

Преимущество: основная логика не блокирует другие потоки

3. Способ 3: ThreadLocal (если нужны раздельные счетчики для каждого потока)

@Service
public class UserService {
    private ThreadLocal<Integer> threadLocalCount = ThreadLocal.withInitial(() -> 0);
    
    public void processUser(Long userId) {
        // Каждый поток имеет свой счетчик
        int count = threadLocalCount.get();
        threadLocalCount.set(count + 1);
        
        System.out.println("Thread " + Thread.currentThread().getName() + 
                          ": call #" + threadLocalCount.get());
        
        // Бизнес логика
        User user = findUser(userId);
        // ...
    }
}

Условие: НЕ потокобезопасно для общего счетчика, но безопасно для каждого потока отдельно

4. Способ 4: AOP для подсчета вызовов

// Аннотация
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CountCalls {
}

// Аспект
@Aspect
@Component
public class CallCountingAspect {
    private Map<String, Integer> callCounts = new HashMap<>();
    private Object lockObject = new Object();
    
    @Around("@annotation(CountCalls)")
    public Object countCalls(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        
        synchronized (lockObject) {
            callCounts.put(methodName, callCounts.getOrDefault(methodName, 0) + 1);
        }
        
        return joinPoint.proceed();
    }
    
    public Map<String, Integer> getCallCounts() {
        synchronized (lockObject) {
            return new HashMap<>(callCounts);
        }
    }
}

// Использование
@Service
public class UserService {
    @CountCalls
    public void processUser(Long userId) {
        // Логика
    }
}

5. Способ 5: Spring Actuator Metrics (рекомендуемый)

// Зависимость в pom.xml
// <dependency>
//     <groupId>org.springframework.boot</groupId>
//     <artifactId>spring-boot-starter-actuator</artifactId>
// </dependency>

@Service
public class UserService {
    private final MeterRegistry meterRegistry;
    
    public UserService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    public void processUser(Long userId) {
        // Увеличиваем счетчик
        meterRegistry.counter(
            "user.process.calls",
            "user_id", userId.toString()
        ).increment();
        
        // Или просто общий счетчик
        meterRegistry.counter("user.process.total").increment();
        
        // Бизнес логика
        User user = findUser(userId);
        // ...
    }
}

// application.yml
management:
  endpoints.web.exposure.include: prometheus
  metrics.enable.jvm: true

Особенности:

  • Потокобезопасно из коробки
  • Интегрируется с Prometheus
  • Видно в /actuator/metrics

6. Способ 6: Переменная уровня класса + synchronized block

@Service
public class UserService {
    private int methodCallCount = 0;
    
    public void processUser(Long userId) {
        synchronized (this) {  // Синхронизируем по this
            methodCallCount++;
        }
        
        System.out.println("Total calls: " + methodCallCount);
        
        // Бизнес логика БЕЗ блокировки
        User user = findUser(userId);
        // ...
    }
    
    @Transactional(readOnly = true)
    public int getCallCount() {
        synchronized (this) {
            return methodCallCount;
        }
    }
}

7. Способ 7: Декоратор (Design Pattern)

public interface UserServiceInterface {
    void processUser(Long userId);
}

@Service
public class UserServiceImpl implements UserServiceInterface {
    @Override
    public void processUser(Long userId) {
        User user = findUser(userId);
        // ...
    }
}

@Component
public class CountingUserServiceDecorator implements UserServiceInterface {
    private int callCount = 0;
    private Object lock = new Object();
    private final UserServiceInterface delegate;
    
    public CountingUserServiceDecorator(UserServiceInterface delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public void processUser(Long userId) {
        synchronized (lock) {
            callCount++;
        }
        
        delegate.processUser(userId);  // Делегируем реальной реализации
    }
    
    public int getCallCount() {
        synchronized (lock) {
            return callCount;
        }
    }
}

8. Пример с использованием в тесте

@SpringBootTest
class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    @Test
    void testMethodCallCount() {
        // Вызываем метод несколько раз
        userService.processUser(1L);
        userService.processUser(2L);
        userService.processUser(3L);
        
        // Проверяем счетчик через Metrics
        double count = meterRegistry.counter("user.process.total").count();
        
        assertEquals(3.0, count);
    }
}

9. Вариант с atomicity без AtomicInteger

@Service
public class UserService {
    private volatile int callCount = 0;
    
    public void processUser(Long userId) {
        // volatile обеспечивает visibility, но не atomicity!
        // Это НЕ потокобезопасно для ++
        
        synchronized (this) {
            callCount++;  // Теперь потокобезопасно
        }
        
        User user = findUser(userId);
    }
}

10. Полный практический пример

@Service
public class AnalyticsService {
    
    // Счетчик для каждого типа операции
    private final Map<String, Integer> operationCounts = new HashMap<>();
    private final Object lock = new Object();
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    public void recordOperation(String operationType) {
        synchronized (lock) {
            operationCounts.put(
                operationType,
                operationCounts.getOrDefault(operationType, 0) + 1
            );
            
            logger.info("Operation {} called {} times", 
                operationType, 
                operationCounts.get(operationType));
        }
    }
    
    public Map<String, Integer> getStatistics() {
        synchronized (lock) {
            return new HashMap<>(operationCounts);  // Копируем, чтобы избежать conflicts
        }
    }
    
    public void resetCounters() {
        synchronized (lock) {
            operationCounts.clear();
        }
    }
}

// Использование
@Service
public class UserService {
    private final AnalyticsService analytics;
    
    @Autowired
    public UserService(AnalyticsService analytics) {
        this.analytics = analytics;
    }
    
    public void processUser(Long userId) {
        analytics.recordOperation("processUser");
        
        // Бизнес логика
        User user = findUser(userId);
        // ...
    }
}

Рекомендации

Для простого случая:

private int count = 0;
private final Object lock = new Object();

synchronized (lock) {
    count++;
}

Для production:

// Используй Spring Actuator/Metrics
meterRegistry.counter("method.calls").increment();

Для распределённой системы:

// Логируй в ELK/Splunk и подсчитай там
logger.info("method.call", Map.of("method", "processUser"));

Когда НЕ использовать Concurrent классы

  • Если явно сказали "без concurrent"
  • Если это простой счетчик в одном сервисе
  • Если потокобезопасность обеспечена на уровне контейнера
  • Если нужна простота (synchronized проще, чем AtomicInteger)

Вывод

Для счетчика вызовов методов в Spring бине БЕЗ concurrent:

  1. Простой способ: synchronized (lock) { count++; }
  2. Правильный способ: Spring Metrics/Actuator
  3. Гибкий способ: AOP аспект для всех методов
  4. Распределённый: логирование в centralized system

Выбирайте в зависимости от требований и масштаба системы.

Как реализуешь счетчик внутри метода по подсчету вхождений в этот метод, находящийся внутри бина в Spring без concurrent? | PrepBro