我有这个方法
@RequestMapping(value = "/affaires",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
@Timed
public ResponseEntity<Affaire> createAffaire(@RequestBody Affaire affaire, HttpServletRequest request) throws URISyntaxException {
Long idMax = affaireRepository.getMaxId();
affaire.setReferenceAffaire("AF_"+LocalDate.now().getYear()+" - "+idMax);
Affaire result = affaireRepository.save(affaire);
我的问题:在我的数据库中,我有 2 个具有相同 ReferenceAffaire 的事务。
所以我猜测该方法在同一时间被调用两次,并且每次 idMax 都是相同的。
有没有办法避免这个方法在同一时间被多次调用?
Servlet,根据定义,是一个线程环境。每个请求都在一个线程上处理,因此多个请求意味着多个线程。但即使是两个请求(两个线程)也会带来并发问题的风险。因此每个 servlet 程序员都必须编写线程安全代码。
所以我猜测该方法在同一时间被调用两次,并且每次 idMax 都是相同的。
是的,完全有可能。而且,如果您看到分配了重复的数字,这确实是可能的原因。
synchronized
👉🏽 您必须保护所有未构建为线程安全的共享资源。
在您的特定情况下,这意味着
affaireRepository.getMaxId()
方法需要保护。一种方法是synchronized
。另一种方法是使用 AtomicInteger
。但我们必须了解更多才能做出最佳推荐。
private final AtomicInteger lastIdIssued = new AtomicInteger( 0 );
…
return this.lastIdIssued.incrementAndGet() ;
任何编写 servlet 代码的人都应该阅读、研究并重读 Brian Goetz 等人的书,Java 并发实践。
有没有办法避免这个方法在同一时间被多次调用?
没有。
在早期版本的 Java Servlet 规范中,有一个选项可以告诉 Servlet 容器以单线程模式运行特定的 Servlet。如果该 Servlet 是调用
affaireRepository.getMaxId()
的唯一位置,那么单线程模式确实会阻止同时调用该方法。但这个功能在后来的规范版本中被删除了。单线程会破坏 Java Servlet 技术所承诺的性能。可能还有其他问题,我不记得了。
再次强调,如果您正在编写任何访问非线程安全资源的 Servlet 代码,那么 您必须了解并发性,并学习
synchronized
、volatile
、Atomic…
类、Java 内存模型、执行器框架,避免 Timer
、JSR 236 Java EE 并发实用程序以及其他问题。上面提到的那本书涵盖了其中大部分内容以及关键概念。
并发和线程安全很复杂,学习起来很有挑战性,正确编写也很棘手。但这当然是可能的。现代 Java 为此类工作提供了一些业内最好的设施。
作为记录的替代标识符,请考虑使用通用唯一标识符 (UUID)。线程和并发不会成为生成 ID 值的问题。
顺便说一句,你的代码还有其他问题。
调用 LocalDate.now() 隐式使用 JVM 当前的默认时区。该默认值可以随时更改。因此,如果区域在元旦前后发生变化,您最终可能会按照不按顺序分配给前一年的序列号。
相反,请始终明确指定您所需的时区。
LocalDate.now( ZoneOffset.UTC ) ;
在函数声明中添加
synchronized
。这完全阻止了您想要的事情:想要同时调用此函数的线程被放入队列中并一个接一个地执行
public synchronized ResponseEntity<Affaire> createAffaire(@RequestBody Affaire affaire, HttpServletRequest request) throws URISyntaxException {
请参阅此处的文档。