Edward Peters Edward Peters - 6 months ago 21
Java Question

Re-Check mutable reference for "synchronized" lock?

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?)

Answer

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 an 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();
            }
        }
    }
    synchronized(someRef){
        someRef.roh();
    }
}
...
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.