10

Is there a good way to implement an asynchronous version of synchronized keyword? Obviously the synchronized() keyword will frequently block the current thread. For example:

  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;
    }

  }

I can return a boolean from this block - it's synchronous. Is there a way to do this asynchronously?

Something like this:

  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);
     });

  }

I made up the Object.onLockAcquisition method, but looking for something like that.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • No and why would you do/want that? Your `if(isLocked)` is also unsafe. – Fureeish Feb 07 '19 at 08:10
  • if(isLocked) is atomic, it's fine. Ever worked in a non-blocking environment? – Alexander Mills Feb 07 '19 at 08:11
  • Yes and internally it actually uses low level blocking. When if its atomic, its safe only to check the value itself. Nothing guarantees that it won't become false between your check and the moment you enter the next line - the `r.run()` call. – Fureeish Feb 07 '19 at 08:15
  • @Fureeish when the onLockAcquisition callback fires, you have obtained the lock - it's analogous to entering a synchronized{} block.. – Alexander Mills Feb 07 '19 at 08:15
  • Where did I mention `Object.onLockAcquisition`? I was referring to the very first if statemenents in your getLock methods. – Fureeish Feb 07 '19 at 08:22
  • 2
    `isLocked` doesn't look atomic to me. You're updating it while holding a lock but reading it without holding that same lock. – Slaw Feb 07 '19 at 08:29
  • This question might help you. https://stackoverflow.com/questions/29457146/is-there-a-way-to-synchronize-on-string-variable-in-java-apart-from-using-intern – Dhruv Kapatel Feb 07 '19 at 08:29
  • Even if your made-up `Object.onLockAcquisition` did the right thing, you are still executing the runnable without it if `isLocked` happened to be `true` the very moment before you call `CompletableFuture.runAsync(r);`, which breaks the entire construct. – Holger Feb 08 '19 at 18:40
  • @Holger it really doesn't - in order to write the boolean you need the lock, but to read from it you don't. – Alexander Mills Feb 08 '19 at 21:25
  • So what feature does your construct actually provide? The tasks may run without holding the lock, the value of the boolean variable may change an arbitrary number of times while running, hence is entirely meaningless, so what's the point of it? – Holger Feb 09 '19 at 09:21
  • What's the semantic of the async lock acquisition supposed to be? – Lie Ryan Feb 09 '19 at 09:37
  • Dont worry about the semantics - the only semantics that matter are blocking vs. non-blocking...the synchronized keyword is a blocking call as usual. – Alexander Mills Feb 09 '19 at 09:49
  • 1
    So you are asking for a construct that has no useful effect? Just remove the `synchronized` without any replacement and you get what you are asking for. Unless you describe anything meaningful besides saying "non-blocking". – Holger Feb 10 '19 at 12:35

3 Answers3

6

Have you investigated alternatives? Depending what you're trying to achieve then one - or a combination - of the following might help (or not):

  • double locking alternatives (using volatile + monitor and check the volatile twice, once before locking, once after locking)
  • use AtomicXXX and there compareAndSet/compareAndExchange etc.. methods
  • use the java.util.concurrent.locks
  • use a single threaded executor to execute the critical section
  • use a queue
daniel
  • 2,665
  • 1
  • 8
  • 18
  • ok, i agree with some of these, but since this is a somewhat common use case I was hoping there was a library that handles this already. Single threaded executor wont quite work in my case, but yes thats a good technique. – Alexander Mills Feb 07 '19 at 18:47
  • Single-threaded executor won't solve it by itself, because the current thread will block until it has access to the thread in the pool. Ultimately non-blocking locks need a special implementation. Non-blocking locks would most likely use a queue, that's right. – Alexander Mills Feb 12 '19 at 20:23
  • Can you show an example of how java.util.concurrent.locks might be used to implement a non-blocking lock on an Object's monitor or just a string key? – Alexander Mills Feb 12 '19 at 20:23
  • @AlexanderMills Right - java.util.concurrent.locks don't support directly the functionality you're looking for. But unlike monitors they allow you to test whether a lock is locked, or attempt to acquire the lock within a given delay. – daniel Feb 13 '19 at 10:18
6

The correct solution in terms of Vert.x would be to use SharedData.getLock()
Reason for that is that asynchronicity is part of a specific library, and not of JVM platform.

Unless Vert.x runs in a clustered mode, it will fall back to local lock:

public void getLockWithTimeout(String name, long timeout, Handler<AsyncResult<Lock>> resultHandler) {
    ...
    if (clusterManager == null) {
      getLocalLock(name, timeout, resultHandler);
    } else {
      ...
    }
  }

getLock uses LocalAsyncLocal underneath:

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

acquire() uses ConcurrentHashMap.compute under the hood: https://github.com/eclipse-vertx/vert.x/blob/master/src/main/java/io/vertx/core/shareddata/impl/LocalAsyncLocks.java#L91

So if you really want to have your own implementation, you can take inspiration from the code above.

Alexey Soshin
  • 16,718
  • 2
  • 31
  • 40
  • will prob accept this when the bounty period ends but gonna see if anyone comes up with alternatives – Alexander Mills Feb 10 '19 at 20:48
  • Yeah I think it would be a good idea to have a implementation for this in vert.x, something async that also plays nicely with the synchronized keyword too – Alexander Mills Feb 12 '19 at 20:14
  • `synchronized` plays on a different level than asynchronous frameworks, so I'm not sure it will work out. – Alexey Soshin Feb 12 '19 at 20:22
  • yes after more reading, I agree, wait() and notify() are for threads, and wait() will block and notify will wake up another thread - those won't work I agree – Alexander Mills Feb 12 '19 at 20:25
  • Unless we are guaranteed a single-threaded environment without pre-emption, I don't think there is a way to avoid using synchronized(){}, because we'd need to synchronize access to the locking library across threads obviously, so kinda back to square one. – Alexander Mills Feb 12 '19 at 21:07
5

One solution in Vertx is the toolkit's asynchronous locking calls:

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

which looks like:

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

however, this is not really the solution I am looking for, as this is a networked lock, which means it's pretty slow compared to a normal in-memory lock. I only need to create a lock in a single thread not across threads or processes.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • Obtaining lock per thread doesn't make much sense in Vert.x, since your code may be executed on different threads. – Alexey Soshin Feb 09 '19 at 14:28
  • @Alexey Soshin, I disagree..Java is generally a multithreaded environment, we have locking in a single thread all the time, that is in fact what `synchronized()` is for.. – Alexander Mills Feb 09 '19 at 19:35
  • That's if you own the thread. But Vert.x is using event loop for asynchronicity. Do you really want to lock the event loop? Probably not. – Alexey Soshin Feb 10 '19 at 10:17
  • yeah sorry I mispoke, all the locks i am looking for will lock on an object in memory. what i meant is i dont want a networked lock, i just want an in memory lock – Alexander Mills Feb 10 '19 at 20:05
  • See my answer below. Unless you run in cluster mode, your lock will be an in-memory lock. – Alexey Soshin Feb 10 '19 at 20:21
  • yeah i saw that, i guess i am looking for an exposed part of the getLocalLock API - I never want a networked lock even if in clustered mode – Alexander Mills Feb 10 '19 at 20:47