← Назад к вопросам
Как реализовать логику подсчета при последовательной выдаче числа на каждый запрос?
1.0 Junior🔥 11 комментариев
#Кэширование и NoSQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Логика подсчета при последовательной выдаче числа на каждый запрос
Это классическая задача реализации счётчика, который инкрементируется при каждом запросе. Существует несколько подходов в зависимости от требований.
Простое решение с глобальной переменной
public class Counter {
private static int count = 0;
public static synchronized int getNextNumber() {
return ++count;
}
}
// Использование
int num1 = Counter.getNextNumber(); // 1
int num2 = Counter.getNextNumber(); // 2
int num3 = Counter.getNextNumber(); // 3
Проблемы:
- Потокобезопасность достигается через
synchronized, но это медленно - Глобальное состояние — плохая архитектура
- Нельзя перезагрузить без перезапуска приложения
Лучший подход: AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private static final AtomicInteger count = new AtomicInteger(0);
public static int getNextNumber() {
return count.incrementAndGet();
}
}
Преимущества:
- Потокобезопасен без блокировок (lock-free)
- Быстрее чем
synchronized - Атомарные операции гарантируют консистентность
REST API реализация
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicInteger;
@RestController
public class CounterController {
private final AtomicInteger counter = new AtomicInteger(0);
@GetMapping("/api/next-number")
public CountResponse getNextNumber() {
int number = counter.incrementAndGet();
return new CountResponse(number);
}
}
class CountResponse {
private int number;
public CountResponse(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
С использованием базы данных
@Repository
public interface CounterRepository extends JpaRepository<Counter, Long> {
Counter findByName(String name);
}
@Service
public class CounterService {
@Autowired
private CounterRepository repository;
@Transactional
public synchronized int getNextNumber() {
Counter counter = repository.findByName("global");
counter.setCount(counter.getCount() + 1);
repository.save(counter);
return counter.getCount();
}
}
Проблема: синхронизированный метод блокирует поток. Лучше использовать SELECT FOR UPDATE:
@Repository
public interface CounterRepository extends JpaRepository<Counter, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT c FROM Counter c WHERE c.name = :name")
Counter findByNameForUpdate(@Param("name") String name);
}
@Service
public class CounterService {
@Transactional
public int getNextNumber() {
Counter counter = repository.findByNameForUpdate("global");
counter.setCount(counter.getCount() + 1);
return counter.getCount();
}
}
С использованием Redis (для распределённых систем)
import org.springframework.data.redis.core.RedisTemplate;
@Service
public class CounterService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public long getNextNumber() {
return redisTemplate.opsForValue().increment("counter");
}
}
Преимущества:
- Распределённый счётчик
- Очень быстро (в памяти)
- Масштабируется на несколько серверов
Последовательность с префиксом (например, ID документов)
public class DocumentIdGenerator {
private final AtomicLong sequence = new AtomicLong(0);
private final String prefix;
public DocumentIdGenerator(String prefix) {
this.prefix = prefix;
}
public String getNextId() {
long num = sequence.incrementAndGet();
return String.format("%s-%010d", prefix, num);
}
}
// Использование
DocumentIdGenerator generator = new DocumentIdGenerator("DOC");
String id1 = generator.getNextId(); // DOC-0000000001
String id2 = generator.getNextId(); // DOC-0000000002
Сравнение подходов
| Подход | Потокобезопасность | Производительность | Масштабируемость | Сложность |
|---|---|---|---|---|
synchronized | ✅ | ❌ низкая | ❌ нет | простая |
AtomicInteger | ✅ | ✅ высокая | ❌ один сервер | простая |
БД + @Transactional | ✅ | ⚠️ средняя | ✅ несколько | средняя |
БД + SELECT FOR UPDATE | ✅ | ✅ хорошая | ✅ несколько | средняя |
| Redis | ✅ | ✅ очень высокая | ✅ полная | простая |
Рекомендации
- Один сервер, однопоточное →
AtomicInteger - Один сервер, многопоточное →
AtomicIntegerилиsynchronized - Несколько серверов → Redis или БД с
SELECT FOR UPDATE - Нужна персистентность → БД
- Максимальная производительность → Redis
Итоги
- AtomicInteger — простое и эффективное решение
- SELECT FOR UPDATE — для БД с гарантией консистентности
- Redis — для распределённых систем
- synchronized — только если нет альтернатив
- Выбирайте подход в зависимости от требований масштабируемости