【问题标题】:CompletableFuture and locksCompletableFuture 和锁
【发布时间】:2017-05-30 13:30:39
【问题描述】:

我有原同步码,

Lock lock = getLock(userId); // get a lock from guava Striped locks
lock.lock();

try {
   // do some database writing action
   updateUserByUserId(userId);
   return updateUserPropertiesByUserId(userId);
}
finally {
    lock.unlock();
}

锁定的目的是在更高级别模拟悲观数据库锁定。

在 Java8 中,引入了 CompleteableFutures。 updateUserByUserId(userId);updateUserPropertiesByUserId(userId); 现在可以返回 CompletableFuture<Void> 以实现完全异步。

我的问题是,我怎样才能使用相同的机制?还是这种锁定方式完全错误? (我真的不想依赖数据库的锁定。如果可能的话,我想在应用层而不是数据库层处理这个)

我试过了

Lock lock = getLock(userId); // get a lock from guava Striped locks

return CompletableFuture
        .supplyAsync(() -> {
            lock.lock();
        })
        .thenCompose(VOID -> updateUserByUserId(userId))
        .thenCompose(entity -> updateUserPropertiesByUserId(userId))
        .whenComplete((entity, ex) -> {
            lock.unlock();
        });

但我在lock.unlock(); 上收到了IllegalMonitorStateException,这是意料之中的,因为您不应该解锁不同线程中的锁。

有什么建议吗?

【问题讨论】:

  • 你的锁在模仿悲观锁,而不是乐观。不过,我不明白为什么您不依赖数据库事务和锁定。如果您的应用程序有多个实例正在运行,或者其他人正在访问同一个数据库,那么您的锁定机制将不起作用。
  • 您对锁的说法是乐观的。我把它改成悲观了。这是一个简单的示例来演示我正在尝试做的事情。实际上,我的数据库只有 1 个访问器,这是我的微服务,只有 1 个接口。在主动-主动设置中,将使用从缓存层检索到的锁。为什么我要这样做是因为我想“快速失败,尽早失败”,而不是再跳到数据库,这会引发错误,让应用层将数据库错误转换为异常,然后捕获它例外。这是一个相当大的打击。
  • 并且 DB 错误转换为 Java 异常可能因 DB 不同而异……甚至同一个 DB 的不同风格。如果可能的话,我真的不想处理。
  • 把这个线性操作分成四个阶段是没有意义的,它仍然是一系列直接依赖的操作,你只会让它变得更复杂和容易出错,没有任何好处。这两个更新方法仅在单独调用时是异步的,这将违反您的锁定要求。在您的 CompletableFuture 链中,您正在等待每个操作完成,然后根据需要启动下一个操作,这使得整个序列像以前一样线性。
  • 哦,有道理!让我重温一下

标签: java multithreading locking guava completable-future


【解决方案1】:

您可以为回调的...Async variants 提供一个执行程序,因此为锁定和解锁的回调提供相同的single threaded executor 应确保它们在同一线程上执行。

Lock lock = getLock(userId); // get a lock from guava Striped locks
Executor lockExecutor = Executors.newSingleThreadExecutor();

return CompletableFuture
        .supplyAsync(() -> {
            lock.lock();
        }, lockExecutor)
        .thenCompose(VOID -> updateUserByUserId(userId))
        .thenCompose(entity -> updateUserPropertiesByUserId(userId))
        .whenCompleteAsync((entity, ex) -> {
            lock.unlock();
        }, lockExecutor);

注意:我还没有运行它,希望它有效!

【讨论】:

  • 我认为作者的意图完全相反:将执行分散到多个线程上
  • 我相信这会将执行分散到多个线程上。它只使用同一个线程进行锁定操作。由于作者已经快一年没有回来了,我怀疑我们是否能确定这个答案是否合适。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 2016-03-17
  • 1970-01-01
  • 2015-04-19
相关资源
最近更新 更多