synchronized(){}的异步(非阻塞)版本

10

有没有一种好的方法来实现 synchronized 关键字的异步版本?显然,synchronized() 关键字会经常阻塞当前线程。例如:

  public static boolean getLockSync(Runnable r) {

    if (isLocked) {
      r.run();
      return true;
    }

    synchronized (My.lock) { // this is blocking, could block for more than 1-2 ms
      isLocked = true;
      r.run();
      isLocked = false;
      return false;
    }

  }
我可以从这个块返回一个布尔值 - 它是同步的。有没有一种异步的方法来实现这一点?
类似这样的东西:
  public static void getLockAsync(Runnable r) {

    if (isLocked) {
      CompletableFuture.runAsync(r);
      return;
    }

    Object.onLockAcquisition(My.lock, () -> { // this is non-blocking
           isLocked = true;
           r.run();
           isLocked = false;
           Object.releaseLock(My.lock);
     });

  }

我编写了Object.onLockAcquisition方法,但是正在寻找类似的方法。


不,你为什么要这样做或想要那样做呢?你的 if(isLocked) 也是不安全的。 - Fureeish
如果(isLocked)是原子的,那就没问题了。你曾经在非阻塞环境中工作过吗? - Alexander Mills
是的,实际上它在内部使用低级别的阻塞。如果它是原子性的,仅检查值本身是安全的。没有任何保证,在你检查和进入下一行 - r.run() 调用的那一刻之间它不会变为 false。 - Fureeish
2
“isLocked” 对我来说看起来不是原子操作。您在持有锁的同时更新它,但在没有持有同一锁的情况下读取它。 - Slaw
1
所以你要一个没有任何有用效果的结构?只需删除synchronized而不进行任何替换,您就可以得到您要求的内容。除非您除了说“非阻塞”之外还描述了任何有意义的东西。 - Holger
显示剩余8条评论
3个回答

6
您有没有考虑过其他方案?根据您想要实现的目标,以下方法中的一个或多个可能会有所帮助(也可能不会):
- 双重锁定替代方案(使用 volatile + monitor,在加锁前后检查 volatile 两次) - 使用 AtomicXXX 并使用 compareAndSet/compareAndExchange 等方法 - 使用 java.util.concurrent.locks - 使用单线程执行器来执行关键部分 - 使用队列

好的,我同意其中一些观点,但由于这是一个比较常见的用例,我希望已经有一个库可以处理这个问题。单线程执行器在我的情况下不太适用,但是这确实是一个好的技巧。 - Alexander Mills
单线程执行器本身无法解决问题,因为当前线程将阻塞,直到它获得对池中线程的访问权限。最终,非阻塞锁需要特殊的实现。非阻塞锁很可能会使用队列,没错。 - Alexander Mills
你能展示一下如何使用java.util.concurrent.locks来实现一个非阻塞锁,用于对象的监视器或仅限于字符串键的情况吗? - Alexander Mills
@AlexanderMills 对的 - java.util.concurrent.locks 不直接支持您正在寻找的功能。但与监视器不同,它们允许您测试锁定是否已锁定,或尝试在给定延迟内获取锁定。 - daniel

6
在Vert.x中,正确的解决方案是使用SharedData.getLock()。原因是异步性是特定库的一部分,而不是JVM平台的一部分。
除非Vert.x以集群模式运行,否则它将退回到本地锁。
public void getLockWithTimeout(String name, long timeout, Handler<AsyncResult<Lock>> resultHandler) {
    ...
    if (clusterManager == null) {
      getLocalLock(name, timeout, resultHandler);
    } else {
      ...
    }
  }

getLock 使用 LocalAsyncLocal 技术实现:

localAsyncLocks.acquire(vertx.getOrCreateContext(), name, timeout, resultHandler);

acquire() 在底层使用 ConcurrentHashMap.computehttps://github.com/eclipse-vertx/vert.x/blob/master/src/main/java/io/vertx/core/shareddata/impl/LocalAsyncLocks.java#L91

如果你真的想要自己实现,可以从上面的代码中汲取灵感。


等到赏金期结束可能会接受这个,但我想看看是否有其他的替代方案。 - Alexander Mills
是的,我认为在vert.x中实现这个功能是一个好主意,需要一些异步操作,并且还要与synchronized关键字很好地配合。 - Alexander Mills
synchronized 在与异步框架不同的层面上发挥作用,所以我不确定它是否能够奏效。 - Alexey Soshin
经过更多的阅读,我同意,wait()和notify()是用于线程的,wait()会阻塞,而notify()会唤醒另一个线程 - 这些不起作用,我同意。 - Alexander Mills
除非我们保证在没有抢占的单线程环境下运行,否则我认为没有办法避免使用 synchronized(){},因为我们显然需要在线程之间同步访问锁定库,所以回到了原点。 - Alexander Mills

5

Vertx中的一个解决方案是工具包的异步锁调用:

https://vertx.io/docs/vertx-core/java/#_asynchronous_locks

看起来像这样:

sd.getLock("mylock", res -> {
   Lock lock = res.result();
   vertx.setTimer(5000, tid -> lock.release());
});

然而,这并不是我正在寻找的解决方案,因为这是一种网络锁,这意味着它与普通的内存锁相比速度要慢得多。我只需要在单个线程中创建一个锁,而不是跨线程或进程。


在Vert.x中,每个线程获取锁并不是很有意义,因为您的代码可能会在不同的线程上执行。 - Alexey Soshin
@Alexey Soshin,我不同意。Java通常是一个多线程环境,我们一直在单个线程中进行锁定,这实际上就是synchronized()的作用。 - Alexander Mills
这是针对你拥有线程的情况。但是Vert.x使用事件循环来实现异步性。你真的想锁定事件循环吗?可能不是。 - Alexey Soshin
是的,抱歉我说错了,我要找的所有锁定都将锁定在内存中的对象上。我的意思是我不想要一个网络锁,我只想要一个内存锁。 - Alexander Mills
除非您在集群模式下运行,否则您的锁将是内存锁。 - Alexey Soshin
是的,我看到了,我想我正在寻找getLocalLock API的公开部分 - 即使在集群模式下,我也永远不想要网络锁。 - Alexander Mills

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接