假设我有一个名为foo的java类,其中包含一个 ConcurrentHashMap
叫h。
也假设 Foo
类有两个定义如下的方法:
public void fooMethod1() {
synchronized(this.h.get("example")) {
...
}
}
public void fooMethod2() {
synchronized(this.h.get("example")) {
...
}
}
假设现在它被称为第一 fooMethod1()
之后呢 fooMethod2()
从两个不同的线程。
我不知道有没有可能 this.h.get("example")
呼叫 fooMethod1()
以及上面返回的对象的同步 get
,可以有 this.h.get("example")
呼叫 fooMethod2()
.
2条答案
按热度按时间ff29svar1#
我不是这方面的Maven,但你的代码在我看来是线程安全的。
在你的片段中,我假设
ConcurrentMap
命名h
已经存在并且永远不会被替换,因此对于该对象是否存在,我们没有cpu核心缓存可见性问题。所以不需要标记ConcurrentMap
作为volatile
.你的
h
Map是一个ConcurrentHashMap
,这是一个ConcurrentMap
. 所以多线程调用get
方法是安全的。我假设我们确定键存在Map
"example"
. 以及ConcurrentHashMap
不允许空值,因此如果您将密钥放入Map中,则必须有一个值供我们检索和锁定。两个方法在从并发Map中检索的对象的同一个内在锁上同步。因此,不同线程中的两个方法中的任何一个首先访问从Map中检索到的对象的方法都将获得一个锁
synchronized
而另一个线程则等待该锁被释放。当然,我假设"example"
在线程运行过程中没有变化。这个
get
方法必须返回完全相同的对象才能使两个线程同步。这是我在你的计划中看到的主要弱点。我建议您在协调两个线程时采用不同的方法。但是,从技术上讲,如果所有这些条件都成立,那么您当前的代码应该可以工作。示例代码
下面是一个完整的代码示例。
我们建立您的
Foo
对象,它在其构造函数中示例化并填充ConcurrentMap
命名map
(而不是h
在代码中)。然后我们启动一对线程,每个线程调用两个方法中的一个。
我们立即休眠第二个方法,以帮助确保第一个线程继续前进。我们不能确定哪个线程先运行,但是长时间的睡眠可以帮助它们进入我们为这个实验准备的顺序。
当第二个方法在其线程中休眠时,其线程中的第一个方法获取
String
包含“猫”字。我们通过调用get
在ConcurrentMap
.然后,第一个方法在持有此锁时进入睡眠状态。通过查看控制台上的输出,我们可以推断其线程中的第二个方法必须处于等待状态,等待
"cat"
弦的锁。最终第一个方法会唤醒、继续并释放cat锁。通过控制台输出,我们可以看到第二个方法的线程获得cat锁并继续其工作。
这段代码使用了projectloom提供的简单的try-with-resources语法和虚拟线程。我正在运行基于早期访问Java16的ProjectLoom的初步构建。但是织布机的东西在这里是无关紧要的,这个演示可以与旧的学校代码。这里的项目代码更简单、更干净。
当你跑的时候。
注意:文本发送到
System.out
并不总是按预期顺序在控制台上打印。验证时间戳以确定何时运行了什么。在本例中,第三行Starting fooMethod2
实际上发生在第二行之前fooMethod1 got the intrinsic lock
.所以我会手动将它们按时间顺序重新排列。
pprl5pva2#
我不知道有没有可能
this.h.get("example")
呼叫fooMethod1()
而上述get返回的对象的同步,可以有this.h.get("example")
呼叫fooMethod2()
.是的,在你指定的点上可能有交错。
这个
synchronized
互斥是基于各自的结果get
电话,不是电话get
他们自称。因此,如果第三个线程正在更新Map
get("example")
调用可以返回不同的值,并且不会在同一Map条目上得到互斥。其次,在下面的片段中:
只有
{ ... }
块得到互斥。第三件要注意的是
this.h
不保证线程安全,除非h
已声明为final
.最后,几乎不可能判断这是否是线程安全的。线程安全性是一个很难精确定义的属性,但非正式地说,它意味着无论线程数多少,代码都将按预期(或指定)运行,并且对于所有可能的执行交织模式都是如此。
在您的示例中,您没有提供足够的代码,也没有清楚地说明您的期望是什么。