Java 如何正确使用ConcurrentHashMap.ComputeFabSent只创建一次资源? 问题

Java 如何正确使用ConcurrentHashMap.ComputeFabSent只创建一次资源? 问题,java,postgresql,multithreading,Java,Postgresql,Multithreading,我很可能在web服务实现的某个地方出现死锁,多个线程可能试图创建同一个资源,由于各种原因,该资源只需要创建一次。我只是有点确定以下是正确的地方,因为这是新添加/更改的代码,之后每隔一段时间就会观察到死锁。虽然,它可能在其他地方,但我自己并不完全确定这个具体的实现 这个问题的目的是以我的具体实现为例,回答一些关于所用技术的问题。充其量,有人会简单地指出我还没有发现自己的僵局,并解释我的错误假设 总体背景 网络服务 一些高级web服务以不同的格式提供不同类型的监控数据。重要的是,调用方可以提供多种感

我很可能在web服务实现的某个地方出现死锁,多个线程可能试图创建同一个资源,由于各种原因,该资源只需要创建一次。我只是有点确定以下是正确的地方,因为这是新添加/更改的代码,之后每隔一段时间就会观察到死锁。虽然,它可能在其他地方,但我自己并不完全确定这个具体的实现

这个问题的目的是以我的具体实现为例,回答一些关于所用技术的问题。充其量,有人会简单地指出我还没有发现自己的僵局,并解释我的错误假设

总体背景 网络服务 一些高级web服务以不同的格式提供不同类型的监控数据。重要的是,调用方可以提供多种感兴趣的数据,并请求以多种不同格式呈现每个结果。这些格式是CSV、JSON、XML、HTML,甚至是预构建的电子邮件,这意味着一些渲染非常耗时。大多数数据彼此完全独立,且格式始终相同,因此使用一些线程池以任意顺序并行读取和呈现所有数据。结果的顺序是按照预期使用和工作

博士后 值得一提的是,对一个web服务实现的每次调用都会创建一个到Postgres的JDBC连接,并在请求的整个生命周期中维护一个事务。这一连接实际上在所有线程之间共享,因此每个更改都由一个事务维护。此外,特别是在计算监控数据期间,使用临时表提供数据,作为生成多个不同报告的基础。这使我可以稍微轻松地计算数据一次

从理论上讲,这种方法应该是安全的,因为JDBC驱动程序应该同步多个线程,而这些线程似乎是同步的。我已经讨论了我的实现的这一方面,并在稍后再次回顾,所以让我们集中讨论这个问题的主题。如果这里没有比赛条件,我还是要去别的地方看看

为什么? 我不知道哪个线程首先尝试创建一些特殊的临时表,但我知道所有这些表都有唯一的名称,因此可以轻松地用作某些映射的键
computeIfAbsent
似乎提供了我所需要的,即每个键只执行一次,而不管请求的数量:

整个方法调用以原子方式执行,因此每个键最多应用一次函数

我想知道的是下面这句话:

在计算过程中,其他线程在此映射上尝试的某些更新操作可能会被阻止,,因此计算应该简短。[…]

我的计算不一定短而简单,但可能需要几分钟。因此,我没有将计算本身放入该方法调用中,而是使用一个额外的资源管理,将访问限制为仅一个线程。因此,总体目标是仅使用
ConcurrentHashMap.computeIfAbsent
实现快速资源管理,并根据
CountDownLatch
实际执行在外部创建临时表的操作。在某些情况下,所有线程都可以简单地确保有必要的表可用

一些代码 问题 为什么映射函数需要短而简单?这真的仅仅是出于性能原因,不在必要时长时间阻塞其他线程,还是实施了一些额外的限制,如超时或类似的限制

我的方法是使用额外的资源管理和映射功能来保持事情的简短和简单,这在理论上是正确的吗?使用
倒计时锁存器在理论上有什么问题吗?或者我是被迫将实现本身放入回调中,而不管计算需要多少时间

在给定的实现中,您是否看到任何可能的死锁?例如,我想知道减少闩锁并随后从映射中移除密钥的顺序。但是我看不出改变顺序会有什么不同,尽管我想,在原子上使用
computeIfPresent
和返回
null
是否都是一个好主意。这应该也会移除钥匙,而且永远不会出现新检索打开的闩锁之类的情况

谢谢你的帮助

public void createIf()
{
    // Concurrently querying for an existing table is always safe, only its creation needs to be
    // synchronized.[...]
    if (this.has())
    {
        return;
    }

    // It's important to distinguish between if we created the latch or am only using it, as the
    // creator of it is the only one to actually execute the query to create some table.
    String          pendingKey  = this.buildPendingKey();
    AtomicBoolean   isNewLatch  = new AtomicBoolean(false);
    CountDownLatch  latch       = DbCacheByTt.PENDING.computeIfAbsent
    (
        pendingKey, (k) ->{ isNewLatch.set(true); return new CountDownLatch(1); }
    );

    // Multiple threads might have passed the first check already when only ever ONE of those
    // should be allowed to create the table.[...]
    if (isNewLatch.get() && !this.has())
    {
        this.getDbConn().getJooq().execute(this.createStep());
        this.debugLogRowsCntIf();

        latch.countDown();
        DbCacheByTt.PENDING.remove(pendingKey);
    }

    try
    {
        latch.await();
    }
    catch (InterruptedException e)
    {
        throw new RuntimeException(e);
    }
}