java如何处理Web服务中的竞争条件?
我用Javaservlet实现了一个Web服务
我得到了以下设置: 有一个处理“作业”条目的数据库。每个作业的状态类似于“正在执行”、“队列中”或“已完成”。如果用户启动了一个新作业,则会在数据库中创建一个具有作业和“队列中”状态的条目
仅当已执行的其他作业少于五个时,才应执行该作业。如果有五个其他人已经在执行,则状态需要保持“在队列中”,并且Cronjob稍后将处理此作业的执行
现在我只是想知道,如果此时执行的作业少于五个,那么我的脚本将执行这个作业。但如果同时,在我询问数据库正在执行多少个作业的脚本和开始执行该作业的脚本之间,另一个用户的另一个请求创建了一个作业,并从数据库中获得了“四个正在执行的作业”,该怎么办
然后会有一个竞争条件,执行6个作业
我怎样才能防止这样的事情发生? 有什么建议吗?非常感谢
# 1 楼答案
盖伊·格林是对的,你所要求的是一种可以用semaphores解决的相互排斥的局面。这个由Dijkstra构成的构造应该可以解决您的问题
此构造通常用于代码,一次只能由一个进程执行。举例来说,情况正是你所面临的;e、 g.需要确保不会丢失更新或脏读的数据库事务。为什么你要同时执行5次死刑?您确定在允许同时执行时不会遇到这些问题吗
基本思想是在代码中有一个所谓的关键部分,它必须受到保护,不受竞争条件的影响。需要互斥处理。代码的这一部分被标记为关键代码,在执行之前,它会告诉其他也希望将其调用到
wait()
的方。一旦完成了它的魔术,它就会调用notify()
,现在一个内部处理程序允许下一个进程执行关键部分但是:
我强烈建议不要自己实施任何互斥处理方法。几年前,在一堂理论计算机科学课上,我们在操作系统级别上分析了这些结构,并证明了可能出现的错误。即使乍一看它看起来很简单,但它的意义远不止于外表,而且取决于语言,如果你自己动手,就很难做到正确。特别是在Java和相关语言中,您无法控制底层VM的工作。相反,有一些预先实现的开箱即用的解决方案已经过测试并证明是正确的
在处理生产环境中的互斥之前,请阅读一些关于互斥的内容,并确保理解它的含义。例如,有一本The Little Book of Semaphores是一本写得很好、读起来很好的参考书。至少看一眼
我不太清楚Java servlet,但Java确实有一个现成的解决方案,用于在名为
synchronized
的关键字中相互排除,以标记代码中不允许多个进程同时执行的关键部分。将不需要外部库前面的文章this提供了一个很好的示例代码。尽管上面已经提到了,但我要提醒你,如果你和几个生产商/消费者打交道,就要真正使用
notifyAll()
,否则会发生奇怪的事情,饥饿中的疯狂过程会来杀死你的猫关于这个主题的另一个更大的教程可以在here找到
# 2 楼答案
您可以使用记录锁定来控制并发性。一种方法是执行“selectforupdate”查询
您的应用程序必须有另一个存储worker\u计数的表。然后,servlet必须执行以下操作:
获取数据库连接
关闭自动提交
插入状态为“队列中”的作业
执行“从…为更新选择工作线程”查询
(此时,执行相同查询的其他用户将不得不等待,直到我们提交)
读取工作线程值
如果工人_cnt>;=5.承诺并退出
(此时您获得了执行作业的票证,但其他用户仍在等待)
将作业更新为“正在执行”
增量工作人员\u cnt
承诺
(此时,其他用户可以继续查询,并将获得更新的worker\u cnt)
一定要执行作业
将作业更新为“已完成”
减量工人_cnt
再次提交
关闭数据库连接
# 3 楼答案
编辑:我现在明白你的问题了。 我做了另一个回答:)
是的,你可以有比赛条件。 您可以使用数据库锁来处理它们。 如果不经常以并发方式访问记录,请查看悲观锁。 如果记录经常以并发方式访问,请查看乐观锁
# 4 楼答案
正如其他人所回应的那样,这种情况需要一个信号量或互斥量。我认为您可能需要注意的一个方面是,权威互斥体的位置。根据具体情况,您可能会有几种不同的最佳解决方案(在安全性与性能/复杂性之间进行权衡):
a)如果您只有一台服务器(非集群),并且修改数据库的唯一用例是通过Servlet,那么您可以实现一个静态内存互斥体(一些可以同步访问的公共对象)。这对性能的影响最小,并且最容易维护(因为所有相关代码都在您的项目中)。此外,它并不取决于您正在使用的特定数据库的特性。它还允许您锁定对非数据库对象的访问
b)如果您将有几个单独的服务器,但它们都是代码的实例,那么您可以实现一个同步服务,该服务允许特定实例在更新数据库之前获得锁(可能有一个超时)。这将有点复杂,但所有逻辑仍将驻留在代码中,并且解决方案将跨数据库类型进行移植
c)如果数据库可以由服务器或其他后端进程(例如ETL)更新,那么唯一的方法就是在数据库中实现记录级锁定。如果您这样做,您将依赖于您的数据库提供的特定类型的支持,并且如果您碰巧将端口连接到其他数据库,则可能需要进行更改。在我看来,这是最复杂、最不可维护的选项,并且只有在c)的条件明确正确时才应该使用它
# 5 楼答案
如果我理解正确,并且您可以控制向DB发出请求的应用程序层,那么您可以使用信号量来控制谁正在访问DB
在某种程度上,信号灯就像交通信号灯。它们只允许N个线程访问关键代码。因此,您可以将N设置为5,并且只允许关键代码中的线程将其状态更改为
executing
等Here是一个很好的关于使用它们的教程
# 6 楼答案
答案隐含在您的问题中:您的请求必须排队,因此请与生产者和消费者建立fifo队列
servlet总是在队列中添加作业(可以选择检查是否已满),另外5个线程每次提取一个作业,如果队列为空,则休眠
没有必要为此使用cron或mutex,只需记住同步队列,否则使用者可能会提取同一作业两次