0

Given is the following pseudo code. A function may be entered by multiple threads simultaneously. I want all threads to execute a() and c(), but b() must only be executed by those threads that were not locked when entering the synchronized block.

In other words: If a thread must wait for the lock, then I want it to wait until the lock is released and then jump over b() and continue with c() right away.

    public void code() {
        a() // check if page is present locally

        if (pageMissing) {
            synchronized (this) {
                page = b(); // get page over REST
            }
        }

        c() // access page
    }

How is this useful? Imagine b() calls an external REST function to update local data. When a thread enters, we want to be sure that the function is called and local data is updated, but once the blocking thread exits b() we know local data is current and we don't want to waste resources in having consecutive threads that were already waiting call the update function again.

Oliver Hausler
  • 4,900
  • 4
  • 35
  • 70
  • How in detail do you "know" it's current? – chrylis -cautiouslyoptimistic- Oct 01 '19 at 03:48
  • Why don't you have a volatile boolean flag (or AtomicBoolean) and check for the flag before calling `b()` *inside* the synchronzied block. The thread that calls b(), once succeeds, will set that flag. All subsequent threads, will skip calling `b()` if that flag is set. But this **cannot** prevent the blocked threads from entering the block. – Thiyagu Oct 01 '19 at 03:50
  • Are you concerned with multiple threads calling `b()` unnecessarily or threads being blocked? If it is the former, my above comment should work. – Thiyagu Oct 01 '19 at 03:53
  • @chrylis I do paging, where I load data pages to local memory over REST. If a page is present, the synchronized block isn't entered at all. Only when a thread wants to read the next page which isn't available locally, I want the first thread to trigger the REST call, and I want all other threads to wait until the call returns and the local data is updated. Every page is only pulled to local once, so all threads that wait do so for the same page. Does that make sense? – Oliver Hausler Oct 01 '19 at 03:58
  • @user7 Yes, I am concerned with threads calling `b()` unnecessarily, but (see my other comment) I need those threads that arrive while `b()` runs to wait until it is finished before they continue with `c()`. – Oliver Hausler Oct 01 '19 at 04:01
  • So why not use something like [Caffeine](https://github.com/ben-manes/caffeine) that have this problem solved already? (It also sounds like all you might need is a `CompletableFuture`.) – chrylis -cautiouslyoptimistic- Oct 01 '19 at 04:07
  • I added comments above so it becomes more clear. As you can see, `a()` checks if the desired page is present locally, if yes, it proceeds directly, otherwise it enters the synchronized block. All pages that enter `b()` will see the page once the blocking thread has passed, but they have already passed the `if` statement at that point. – Oliver Hausler Oct 01 '19 at 04:07
  • @chrylis Yes, Caffeine or Guava Cache would probably solve this with CacheLoader. – Oliver Hausler Oct 01 '19 at 04:11

1 Answers1

1

You can have a boolean flag (instance variable) that denotes whether b() has been called or not. All threads that enter the synchronized block will check this condition before calling b().

private boolean isDone = false;

public void code() {
    a();
    synchronized (this) {
        if (!isDone) {
            b();
            isDone = true;
        }
    }
    c();
}

Only the first thread that acquires the lock will call b(). After it succeeds, it will set the flag to true and will exit the block. All subsequent threads entering the block will fail on the if condition and hence will not invoke b().


If you do not want the other threads to wait on the synchronized block's lock and want to continue to call c(), you can remove the synchronized keyword and use an AtomicBoolean and use its compareAndSet

 private AtomicBoolean isDone = new AtomicBoolean(false);

 public void code() {
    a();
    if (isDone.compareAndSet(false, true)) {
       b();
    }
    c();
}

Javadoc of compareAndSet states

Atomically sets the value to the given updated value if the current value {@code ==} the expected value.

@return {@code true} if successful. False return indicates that the actual value was not equal to the expected value.

So, the first thread that calls compareAndSet while isDone is false will succeed. The others will get false as the return value and hence won't enter the block and can thus continue.


UPDATE: From your comments

@user7 Yes, I am concerned with threads calling b() unnecessarily, but (see my other comment) I need those threads that arrive while b() runs to wait until it is finished before they continue with c()

Seems you need the first solution of mine.

Community
  • 1
  • 1
Thiyagu
  • 17,362
  • 5
  • 42
  • 79
  • Yes, first is what I need. I had hoped something "magic" exists, that elegantly does exactly this. But the volatile variable seems to be the way then. – Oliver Hausler Oct 01 '19 at 05:17
  • 2
    You don't need to make `isDone` volatile. The synchronized block already creates the "happens-before" relationship between threads that you need to avoid data races (see the Java Memory Model); making `isDone` volatile does nothing at best, and at worst causes additional overhead due to extra memory-cache related operations by the CPU. – Erwin Bolwidt Oct 01 '19 at 05:40
  • 1
    @ErwinBolwidt Yes. Synchronized block provides the happens-before guarantee. Updated the answer. – Thiyagu Oct 01 '19 at 06:52
  • 1
    @OliverHausler There is no need for it to be volatile. See above comment. – Thiyagu Oct 01 '19 at 06:54