Java 同步(hashmap.get(data))是线程安全的吗?

Java 同步(hashmap.get(data))是线程安全的吗?,java,concurrency,synchronization,synchronized,concurrenthashmap,Java,Concurrency,Synchronization,Synchronized,Concurrenthashmap,假设我有一个名为Foo的java类,其中包含一个名为h的ConcurrentHashMap属性 还假设Foo类有如下定义的2个方法: public void fooMethod1() { synchronized(this.h.get("example")) { ... } } public void fooMethod2() { synchronized(this.h.get("example")) {

假设我有一个名为Foo的java类,其中包含一个名为h的
ConcurrentHashMap
属性

还假设
Foo
类有如下定义的2个方法:

public void fooMethod1() {
    synchronized(this.h.get("example")) {
        ...
    }
}

public void fooMethod2() {
    synchronized(this.h.get("example")) {
        ...
    }
}
现在假设它首先从两个不同的线程调用
foometh1()
,然后调用
foometh2()


我不知道在
this.h.get(“示例”)
调用
foodmethod1()
和上述
get
返回的对象的同步之间是否可能存在
this.h.get(“示例”)
调用
foodmethod2()
这方面我不是专家,但是你的代码在我看来是线程安全的

在您的代码片段中,我假设名为
h
ConcurrentMap
已经存在,并且从未被替换,因此对于该对象是否存在,我们没有CPU核心缓存可见性问题。因此无需将
ConcurrentMap
标记为
volatile

您的
h
地图是a,它是a。因此,多个线程同时调用
get
方法是安全的

我假设我们确定键
“示例”
存在映射。而且
ConcurrentHashMap
不允许空值,因此如果您将密钥放入映射中,则必须有一个值供我们检索和锁定

这两个方法在从并发映射检索的对象的同一个内在锁上同步。因此,不同线程中的两个方法中的任何一个首先访问从映射中检索到的对象的方法都将获得一个锁,而另一个线程将等待该锁被释放。当然,我假设
“example”
键的映射条目在线程运行期间没有改变

映射上的
get
方法必须返回完全相同的对象,以便两个线程同步。这是我在你的计划中看到的主要弱点。我建议您在协调两个线程时采用不同的方法。但是,从技术上讲,如果所有这些条件都成立,那么您当前的代码应该可以工作

示例代码 下面是一个完整的代码示例

我们建立您的
Foo
对象,该对象在其构造函数中实例化并填充名为
map
ConcurrentMap
(而不是代码中的
h

然后我们启动一对线程,每个线程调用两个方法中的一个

我们立即使用第二种方法来帮助确保第一个线程继续进行。我们无法确定哪个线程先运行,但是长时间的睡眠可以帮助它们按照我们为这个实验准备的顺序运行

当第二个方法在其线程中休眠时,其线程中的第一个方法获取包含单词“cat”的
字符串的内在锁。我们通过调用
ConcurrentMap
上的
get
以线程安全的方式检索该对象

然后,第一个方法在持有此锁时进入睡眠状态。通过查看控制台上的输出,我们可以推断其线程中的第二个方法必须处于等待状态,等待释放
“cat”
字符串的锁

最终,第一个方法唤醒、继续并释放cat锁。通过控制台输出,我们可以看到第二个方法的线程获得cat锁并继续其工作

这段代码使用了简单的newtry-with-resources语法和虚拟线程。我正在运行一个基于早期访问Java16的项目。但是,织布机的东西在这里是无关紧要的,这个演示可以与旧的学校代码。这里的项目代码更简单、更干净

package work.basil.example;
导入java.time.Duration;
导入java.time.Instant;
导入java.util.concurrent.ConcurrentHashMap;
导入java.util.concurrent.ConcurrentMap;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
公开课Foo
{
私有ConcurrentMapmap=null;
公共食品()
{
this.map=新的ConcurrentHashMap();
这个.map.put(7,“狗”);
这个.map.put(42,“cat”);
}
公共方法1()
{
System.out.println(“在“+Instant.now()处启动fooMethod1”);
已同步(this.map.get(42))
{
System.out.println(“fooMethod1获得了cat字符串的固有锁。”+Instant.now());
//暂停一段时间,以显示另一个线程必须等待字符串“cat”的内在“synchronized”锁。
尝试{Thread.sleep(Duration.ofSeconds(5));}catch(InterruptedException e){e.printStackTrace();}
System.out.println(“在“+Instant.now()处继续使用fooMethod1”);
}
}
公共方法2()
{
System.out.println(“在“+Instant.now()”处启动fooMethod2”);//睡眠以使另一个线程更有可能运行。
尝试{Thread.sleep(Duration.ofSeconds(2));}catch(InterruptedException e){e.printStackTrace();}
已同步(this.map.get(42))
{
System.out.println(“fooMethod2获得了cat字符串的内在锁。”+Instant.now());
System.out.println(“在“+Instant.now()处继续使用fooMethod2”);
}
}
公共静态void main(字符串[]args)
{
System.out.println(“INFO-start-run'main`.”+Instant.now());
Foo-app=新的Foo();
试一试(
ExecutorService ExecutorService=Executors.newVirtualReadExecutor();
)
{
executorService.submit(()->app.fooMethod1());
executorService.submit(()->app.fooMethod2());
}
//在这一点上,控制块的流动直到提交的任务完成
synchronized(this.h.get("example")) {
    ...
}