我有此方法
@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每次都相同。 有没有一种方法可以避免在同一时间多次调用此方法?
答案 0 :(得分:1)
在函数声明中添加synchronized
。这样可以完全避免您想要的东西:希望同时调用此函数的线程被放入队列中并一个接一个地执行
public synchronized ResponseEntity<Affaire> createAffaire(@RequestBody Affaire affaire, HttpServletRequest request) throws URISyntaxException {
在此处查看documentation。
答案 1 :(得分:1)
Servlet是线程环境。每个请求都在一个线程上处理,因此许多请求意味着许多线程。但是即使两个请求(两个线程)也带来了并发问题的风险。因此,每个servlet程序员都必须编写线程安全的代码。
所以我猜想该方法在完全相同的时间被调用了两次,并且每次idMax都相同。
是的,完全有可能。而且,如果您看到重复的数字被分配,这确实是可能的原因。
synchronized
任何未构建为线程安全的共享资源都必须受到保护。在您的特定情况下,这意味着affaireRepository.getMaxId()
方法需要保护。一种实现方法是synchronized
。但是我们必须了解更多信息才能提出最佳建议。
任何编写servlet代码的人都应该阅读,研究和重新阅读Brian Goetz等人的书,Java Concurrency in Practice。
有没有一种方法可以避免在同一时间多次调用此方法?
不。
在Java Servlet规范的早期版本中,有一个选项可以告诉Servlet容器以单线程模式运行特定的Servlet。如果该Servlet是唯一调用affaireRepository.getMaxId()
的地方,那么单线程模式确实会阻止同时调用该方法。但是此功能在规范的更高版本中已删除。单线程破坏了Java Servlet技术所承诺的性能。我不记得了,可能还有其他问题。
同样,如果您正在编写访问非线程安全资源的任何Servlet代码,则您必须了解并发性,并研究synchronized
,volatile
, Java内存模型,执行程序框架,避免了Timer
,Java EE的JSR 236并发实用程序和其他问题。上面提到的书涵盖了大多数内容以及关键概念。
并发性和线程安全性很复杂,学习起来很困难,并且编写起来很棘手。但这当然是可能的。现代Java为此类工作提供了一些业界最佳的工具。
作为记录的替代标识符,请考虑使用Universally Unique Identifier (UUID)。就生成ID值而言,线程和并发不会成为问题。
顺便说一句,您的代码还有其他问题。
调用LocalDate.now()隐式使用JVM的当前默认时区。该默认值可以随时更改。因此,如果区域在元旦前后变化,那么您可能最终会无序分配给上一年的序列号。
相反,请始终明确指定所需的时区。
LocalDate.now( ZoneOffset.UTC ) ;