Java 需要一种即使应用程序重新启动也不会丢失的锁定机制(持久锁定)?

Java 需要一种即使应用程序重新启动也不会丢失的锁定机制(持久锁定)?,java,hibernate,synchronization,locks,Java,Hibernate,Synchronization,Locks,我有下面的场景。有两个应用程序共享一个数据库。这两个应用程序都可以用来更改基础数据库。例如,可以从两个系统修改客户1。我想确保当有人对应用程序1中的客户1执行操作时,我需要为该锁提供一个持久锁,以便应用程序2中的任何人都不能对同一客户执行任何操作。即使这些应用程序中的任何一个宕机,它也应该保持锁定。解决此类问题的正确方法是什么?正如@Turing85的评论所暗示的,这是一个极其危险的领域:如果有人被电源线绊倒,你的应用程序将无法运行,无法再次启动。永久地。至少,在有人介入并手动解决问题之前。这很

我有下面的场景。有两个应用程序共享一个数据库。这两个应用程序都可以用来更改基础数据库。例如,可以从两个系统修改客户1。我想确保当有人对应用程序1中的客户1执行操作时,我需要为该锁提供一个持久锁,以便应用程序2中的任何人都不能对同一客户执行任何操作。即使这些应用程序中的任何一个宕机,它也应该保持锁定。解决此类问题的正确方法是什么?

正如@Turing85的评论所暗示的,这是一个极其危险的领域:如果有人被电源线绊倒,你的应用程序将无法运行,无法再次启动。永久地。至少,在有人介入并手动解决问题之前。这很少是你想要的

通常的解决方案是在DB级别进行锁定:如果是“单一文件即数据库”模型,如H2或SQLite,则让DB引擎锁定文件以进行写入,并将OS级别的文件锁定用作选通机制。这有相当大的优势,如果应用程序A由于任何原因(电源短缺、硬崩溃,谁知道)从空中掉下来,锁就会被释放

如果DB是一个单独的运行进程(psql、mysql、mssql等),那么这些进程都有可以使用的锁定功能

如果这些选项都不可用,您可以手动操作:您可以使用新的文件API创建保证原子/唯一的文件:

int pid = 0; // see below
Path p = Paths.get("/absolute/path/to/agreed/upon/lockfile/location/lockfile.pid");
Files.write(p, String.valueOf(pid), StandardOpenOption.CREATE_NEW);
CREATE\u NEW
open选项要求java确保原子性:要么[A]该文件以前不存在,要么现在存在,是这个过程造成的,要么[B]它将抛出

这个过程没有创造它,但不幸的是另一个过程在同一时间做同样的事情,并且也创造了它,其中一个过程现在覆盖了另一个过程的努力——这就是创造新的手段和保证:这不会发生。(相对于
CREATE
选项,该选项将覆盖其中的内容,并且不保证原子性)

现在,您可以将文件系统用作全局唯一锁:要获取锁,您需要创建该文件。如果可以的话,太好了。你明白了。如果您不能,那么您必须等待(如果您希望尽快获取它,那么需要使用watcher API或循环,这不是一个好的选择,与进程内锁定相比,这是一个非常昂贵的操作!)要放弃锁定,只需删除文件即可

为了防止硬崩溃将文件永久地留在那里,防止应用程序再次运行,它可以帮助在其中注册“pid”(进程id)。如果您正在手动修复问题,这将为您提供一些调试,您可以使用自动检查('嘿,操作系统,是否仍有一个id为123981的进程在运行?否?好的,那么它一定是硬崩溃了,并保留了锁文件!')。不幸的是,在java中使用pids是令人费解的,因为java或多或少都是围绕着这样一个概念设计的,即您不应该太依赖底层操作系统,而且java并没有真正假设“进程ID”是底层操作系统所做的事情。谷歌周围如何获得它,你可以这样做

这让我们进入了最后一点,你对不一致性的明显恐惧:毕竟,你实际上表现出一种明显疯狂的想法,即当出现硬崩溃时(进程崩溃,锁没有明确放弃),你希望应用程序永久禁用。我假设您希望这样做是因为您担心数据库处于不一致的状态,并且在手动查看之前,您不希望任何东西再次触碰它

好吧,锁文件业务正是你得到它的方式。然而,这对用户来说是相当不友好的,并且不是必需的:您可以设计数据库和流程(使用事务、仅附加表和日志系统),以便它们始终能够在硬崩溃中安然生存

例如,考虑文件系统。过去,当你在电源线上绊倒时,你会遇到一件很糟糕的事情,系统会进行“全磁盘检查”,很可能会发现一堆错误

但在现代系统中,情况已不再如此。整天都被电源卡绊倒。你不会得到损坏的文件(除非进程设计糟糕,在这种情况下,损坏是应用程序的错误,而不是文件系统的错误),并且不需要进行大量的磁盘检查

这主要是通过一个称为“日志”的概念来实现的

比方说,您想用文本“再见吧!”替换一个“你好,世界!”的文件。你可以开始写字节。假设你到了“Goodb,World!”然后有人被电缆绊倒了

你现在用水管冲洗。数据不一致,谁知道发生了什么

但想象一个不同的系统:

日记 系统首先生成一个名为“.jobrecord”的文件,并在其中写入:我将打开此文件,并在开始时用“再见,现在!”覆盖数据

然后,它实际上就这样做了

然后,它以原子方式删除作业记录(例如,通过更新单个字节来标记:“完成”)

现在,在启动时,系统可以检查该文件是否存在,如果存在,则检查作业是否实际完成,或者在需要时完成它。瞧,现在你永远不会有一个不一致的系统

你也可以编写这样的工具

备选方案:仅追加 另一种滚动方式是只添加数据,并且具有有效性标记。所以,您永远不会覆盖任何文件,您只会创建新文件,并“将它们旋转到位”。例如,您可以创建一个名为“target.new”的新文件,复制数据,然后用“再见,现在!”覆盖开头,然后在原始“target”上自动重命名文件,而不是在文件上写入