2

I'm wondering if there is an easy way to make a synchronized lock that will respond to changing references. I have code that looks something like this:

private void fus(){
    synchronized(someRef){
        someRef.roh();
    }
}
...
private void dah(){
    someRef = someOtherRef;
}

What I would like to happen is:

  1. Thread A enters fus, and acquires a lock on someref as it calls roh(). Assume roh never terminates.
  2. Thread B enters fus, begins waiting for someRef` to be free, and stays there (for now).
  3. Thread C enters dah, and modifies someRef.
  4. Thread B is now allowed to enter the synchronized block, as someRef no longer refers to the object Thread A has a lock on.

What actually happens is:

  1. Thread A enters fus, and acquires a lock on someref as it calls roh(). Assume roh never terminates.
  2. Thread B enters fus, finds the lock, and waits for it to be released (forever).
  3. Thread C enters dah, and modifies someRef.
  4. Thread B continues to wait, as it's no longer looking at someref, it's looking at the lock held by A.

Is there a way to set this up such that Thread B will either re-check the lock for changing references, or will "bounce off" into other code? (something like sychronizedOrElse?)

Ravindra babu
  • 37,698
  • 11
  • 250
  • 211
Edward Peters
  • 3,623
  • 2
  • 16
  • 39
  • 1
    It sounds like you're synchronizing on the wrong object. – user2357112 May 19 '16 at 20:41
  • Is this curiosity or are you searching for a solution ? – UDKOX May 19 '16 at 20:41
  • @UDOX - Little of each. It's enough of an edge case in my code that it's not a huge concern (if the `roh`-equivalent method fails to terminate, that's probably enough of a problem on its own that my locks messing up aren't the real culprit), but it would be good to know, and it seems like it would be generally useful for avoiding deadlocks. – Edward Peters May 19 '16 at 20:45
  • Re, "Assume roh never terminates." If that is possible, then you should not be calling it from inside a `synchronized` block. If your code ever spends as much as a millisecond (of CPU time, not real-time) in any `synchronized` block, then it's time to re-think the design. – Solomon Slow May 19 '16 at 21:01
  • @EdwardPeters Why would you want to change the object on which to lock? Is it to force the release of a lock by a non-responsive thread? – biziclop May 19 '16 at 21:14
  • @jameslarge It might not make sense with `synchronized` in particular, but it is an interesting question in general. – biziclop May 19 '16 at 21:22
  • 1
    Is this only a one-time scenario ? A(locks)->B(waits)->C(unlock B)->B(awakes) and what afterfards ? If next thread D that calls `fus` must start this scenario agaiin ? – krokodilko May 19 '16 at 21:23
  • @biziclop The initial problem had to do with multiple GUI panels, any one of which might be "visible" at a given time. The visible panel (and only the visible panel) would want to poll for updates about the status of its contents. This polling would occur on a regular basis, but also promptly when the panel came into view; the synchronization is to assure that the timer-initialized and user-initialized polls don't interleave. However, if a different panel became visible, it would be okay for its update() to be called before the prior one completed. someRef refers to the currently visible panel – Edward Peters May 19 '16 at 21:40
  • @kordiko In general, yes, this expected to cycle - several threads may call `fus()` at any time, while another thread will occasionally modify `someref`. – Edward Peters May 19 '16 at 21:49
  • 1
    @james large How would you handle operations that involve commands issued to a networked hub, with the requirement that those commands not interleave and the potential for high latency? – Edward Peters May 19 '16 at 21:51
  • @EdwardPeters Wouldn't a queue be better suited for this kind of problem? – biziclop May 19 '16 at 22:37
  • Not sure what you mean by "networked hub", but if several threads in my program needed to communicate with a remote service, my first choice would be for each thread to have its own independent connection. If I were forced to make them all share one connection, then I would add a new thread whose sole responsibility was to communicate with the remote system and, which acted as a proxy to the remote service for the other threads. – Solomon Slow May 20 '16 at 13:21

3 Answers3

3

There surely is a way, but not with synchronized. Reasoning: At the point in time, where the 2nd thread enters fus(), the first thread holds the intrinsic lock of the object referenced by someRef. Important: the 2nd thread will still see someRef referencing on this very object and will try to acquire this lock. Later on, when the 3rd thread changes the reference someRef, it would have to notify the 2nd thread somehow about this event. This is not possible with synchronized.

To my knowledge, there is no built-in language-feature like synchronized to handle this kind of synchronization.

A somewhat different approach would be to either manage a Lock within your class or give someRef an attribute of type Lock. Instead of working with lock() you can use tryLock() or tryLock(long timeout, TimeUnit unit). This is a scheme on how I would implement this (assuming that someRef has a Lock attribute):

volatile SomeRef someRef = ... // important: make this volatile to deny caching
...
private void fus(){
    while (true) {
        SomeRef someRef = this.someRef;
        Lock lock = someRef.lock;
        boolean unlockNecessary = false;
        try {
            if (lock.tryLock(10, TimeUnit.MILLISECONDS)) { // I have chonse this arbritrarily
                unlockNecessary = true;
                someRef.roh();
                return; // Job is done -> return. Remember: finally will still be executed.
                        // Alternatively, break; could be used to exit the loop.
            }
        } catch (InterruptException e) {
            e.printStackTrace();
        } finally {
            if (unlockNecessary) {
                lock.unlock();
            }
        }
    }
}
...
private void dah(){
    someRef = someOtherRef;
}

Now, when someRef is changed, the 2nd thread will see the new value of someRef in its next cycle and therefore will try to synchronize on the new Lock and succeed, if no other thread has acquired the Lock.

Turing85
  • 18,217
  • 7
  • 33
  • 58
2

What actually happens is ... Thread B continues to wait, as it's no longer looking at someref, it's looking at the lock held by A.

That's right. You can't write code to synchronize on a variable. You can only write code to synchronize on some object.

Thread B found the object on which to synchronize by looking at the variable someref, but it only ever looks at that variable one time to find the object. The object is what it locks, and until thread A releases the lock on that object, thread B is going to be stuck.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
1

I would like to add some more info on top of excellent answers by @Turing85 and @james large.

I agree that Thread B continues to wait.

It's better to avoid synchronization for this type of program by using better lock free API.

Atomic variables have features that minimize synchronization and help avoid memory consistency errors.

From the code you have posted, AtomicReference seems to be right solution for your problem.

Have a look at documentation page on Atomic package.

A small toolkit of classes that support lock-free thread-safe programming on single variables. In essence, the classes in this package extend the notion of volatile values, fields, and array elements to those that also provide an atomic conditional update operation of the form:

boolean compareAndSet(expectedValue, updateValue);

One more nice post in SE related to this topic.

When to use AtomicReference in Java?

Sample code:

String initialReference = "value 1";

AtomicReference<String> someRef =
    new AtomicReference<String>(initialReference);

String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);

Refer to this jenkov tutorial for better understanding.

Community
  • 1
  • 1
Ravindra babu
  • 37,698
  • 11
  • 250
  • 211