提问人:Felix Schildmann 提问时间:7/22/2023 更新时间:7/22/2023 访问量:40
如何在 Java 中根据方法的参数有条件地锁定方法?
How do I conditionally lock a method based on its parameter in Java?
问:
我有以下方法为给定参数运行“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());
}
}
答:
synchronized (x) {}
工作原理如下:
引用。这与变量无关,而是它所指向的对象。给定 ,并且功能相同,因为 x 和 y 指向同一个对象。假设它取消引用,如果为 null,则会导致 .
x
Object y = new Object(); Object x = y;
synchronized (y) {}
synchronized (x) {}
x
NullPointerException
所有对象都有一个隐藏的额外字段;它看起来像:。
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 中记录锁定也会停止任何子作业处理,因此强烈建议不要这样做。如果此代码与 在同一个包中,我建议您改为创建一个专用的锁对象并使其成为包私有:JobModel
JobModel
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.concurrent
ReadWriteLock
synchronized
wait()
notifyAll()
管理多核东西的一个严重问题是,它基本上是完全不可测试的——JVM 有各种空间可以随心所欲地运行,但是,在许多系统上,它以一种方式可靠地运行。这意味着,编写今天一整天都能完美运行的代码非常简单。但它永远不会在你朋友的电脑上工作,下周每隔几个小时它就会随机地不起作用。因此,“实验”,通常是学习编程的好方法,在这里效果很糟糕。更有理由使用来自!java.util.concurrent
该问题似乎与问题非常相似 允许单个线程使用 Long 值
一个 requestId(在本例中为 job/jobId)的单线程执行。但是,它不会阻止并移动到下一个 requestId。 提到的解决方案会忽略 jobId,如果它已经在处理中。
评论
runSubJobs
JobModel
runSubJobs
jobModel.getId()
JobModel
this