如何在 Java 中根据方法的参数有条件地锁定方法?

How do I conditionally lock a method based on its parameter in Java?

提问人:Felix Schildmann 提问时间:7/22/2023 更新时间:7/22/2023 访问量:40

问:

我有以下方法为给定参数运行“SubJobs”:JobModel

public void runSubJobs(JobModel jobModel) {
     LOGGER.log("Start executing SubJobs for " + jobModel.getId());
     for (SubJobModel subJobModel : jobModel.getSubJobs()) {
           run(subJobModel);
     }
     LOGGER.log("Finished executing SubJobs for " + jobModel.getId());
}

在当前状态下,该方法可以在不同的线程上并行运行,但我想将其限制为以下条件:

runSubJobs() 应该只为不同的 JobModel 对象并行运行,不能为同一个 JobModel 参数并行执行

解决此问题的最佳实践是什么?我可以像这样包围方法的内容吗?synchronized (jobModel) {}

public void runSubJobs(JobModel jobModel) {
  synchronized (jobModel) {
     LOGGER.log("Start executing SubJobs for " + jobModel.getId());
     for (SubJobModel subJobModel : jobModel.getSubJobs()) {
           run(subJobModel);
     }
     LOGGER.log("Finished executing SubJobs for " + jobModel.getId());
  }
}
Java 锁定 同步

评论

0赞 gdomo 7/22/2023
如果给定已经在另一个线程中运行,该怎么办?等一下再跑?或者,例如,立即返回?runSubJobsJobModelrunSubJobs
1赞 shmosel 7/22/2023
看起来差不多。只是要注意它锁定在实际对象上。如果有两个相等的对象,则出于同步目的,它们仍然是分开的。相反,如果对象被缓存并重用于不同的目的,您最终可能会在不知不觉中共享锁。(出于这个原因,锁定将是一个非常糟糕的主意。此外,如果有任何代码同步 ,您将争夺相同的锁。jobModel.getId()JobModelthis

答:

2赞 rzwitserloot 7/22/2023 #1

synchronized (x) {}工作原理如下:

  • 引用。这与变量无关,而是它所指向的对象。给定 ,并且功能相同,因为 x 和 y 指向同一个对象。假设它取消引用,如果为 null,则会导致 .xObject y = new Object(); Object x = y;synchronized (y) {}synchronized (x) {}xNullPointerException

  • 所有对象都有一个隐藏的额外字段;它看起来像:。private Thread owner = null;

  • synchronized (x) { 尝试运行 - 当然,原子。此外,线程是“可重入”的。 当 X 指向的对象已被标记为“此线程拥有它”时,工作正常。if (x.owner == null) { x.owner = Thread.currentThread(); } else { just keep trying but without wasting all that much CPU cycles checking }synchronized (x) {

  • 以任何方式退出该同步块(运行到 或从中运行,甚至抛出导致它的异常)将运行,从而让等待成为该对象所有者的任何其他被“阻止”的线程继续使用它。}return;break;x.owner = null;

  • 冲突的解决是不确定的不能保证公平。换句话说,如果 5 个线程都因为试图成为同一对象的所有者而阻塞,并且该对象当前归另一个线程所有,并且该线程退出同步块 - 那么 JVM 不会对谁“获胜”做出任何承诺。特别是等待时间最长的线程绝对没有优势。JVM 也不保证它掷出公平的骰子。它只是没有告诉你它是如何决定的,实际上它取决于系统、操作系统、月相,以及你当时正在播放的歌曲。如果你的软件不能正常工作,除非“谁赢了战斗”以某种方式进行,那么你的软件就以一种不可测试的方式存在问题。这很糟糕。


因此:是的,当然应该可以正常工作。通常,您希望锁有很好的文档记录:您应该在 javadoc 中记录锁定也会停止任何子作业处理,因此强烈建议不要这样做。如果此代码与 在同一个包中,我建议您改为创建一个专用的锁对象并使其成为包私有:JobModelJobModel

public class JobModel {
  final Object subJobRunnerLock = new Object[0];
}

public class SubJobRunner {
  public void runSubJobs(JobModel m) {
    synchronized (m.subJobRunnerLock) {
      .. process em here ..
    }
  }
}

这样可以避免让其他代码超出您的直接控制范围,获取锁,从而意外地完全删除您的代码。你很少想锁定公共任何东西

注意:如果您希望 JobModel 可序列化;它只是一个简单的创建微小对象,与 不同,它是可序列化的。如果可序列化性无关紧要,也同样简单。new Object[0]new Object();new Object()


请注意,这是相当低级的东西。该软件包具有各种有用的实用程序,这些实用程序往往工作得更好。例如,有一个工作方式很像这些,但具有“一个编写器多个读取器”设置的额外功能:任何数量的读取器都可以获取锁定并同时运行,但只有一个编写器可以(读取器阻止编写器,编写器阻止读取器)。一个常见的工作,很难手动编写和管理 、 和 (它们是 JVM 中内置的并发管理原语)。java.util.concurrentReadWriteLocksynchronizedwait()notifyAll()

管理多核东西的一个严重问题是,它基本上是完全不可测试的——JVM 有各种空间可以随心所欲地运行,但是,在许多系统上,它以一种方式可靠地运行。这意味着,编写今天一整天都能完美运行的代码非常简单。但它永远不会在你朋友的电脑上工作,下周每隔几个小时它就会随机地不起作用。因此,“实验”,通常是学习编程的好方法,在这里效果很糟糕。更有理由使用来自!java.util.concurrent

0赞 Sagar 7/22/2023 #2

该问题似乎与问题非常相似 允许单个线程使用 Long 值

一个 requestId(在本例中为 job/jobId)的单线程执行。但是,它不会阻止并移动到下一个 requestId。 提到的解决方案会忽略 jobId,如果它已经在处理中。