How do determine if an object is locked (synchronized) so not to block in Java?

Viewed 66169

I have a process A that contains a table in memory with a set of records (recordA, recordB, etc...)

Now, this process can launch many threads that affect the records, and sometimes we can have 2 threads trying to access the same record - this situation must be denied. Specifically if a record is LOCKED by one thread I want the other thread to abort (I do not want to BLOCK or WAIT).

Currently I do something like this:

synchronized(record)
{
performOperation(record);
}

But this is causing me problems ... because while Process1 is performing the operation, if Process2 comes in it blocks/waits on the synchronized statement and when Process1 is finished it performs the operation. Instead I want something like this:

if (record is locked)
   return;

synchronized(record)
{
performOperation(record);
}

Any clues on how this can be accomplished? Any help would be much appreciated. Thanks,

8 Answers

Whilst the above approach using a Lock object is the best way to do it, if you have to be able to check for locking using a monitor, it can be done. However, it does come with a health warning as the technique isn't portable to non Oracle Java VMs and it may break in future VM versions as it isn't a supported public API.

Here is how to do it:

private static sun.misc.Unsafe getUnsafe() {
    try {
        Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public void doSomething() {
  Object record = new Object();
  sun.misc.Unsafe unsafe = getUnsafe(); 
  if (unsafe.tryMonitorEnter(record)) {
    try {
      // record is locked - perform operations on it
    } finally {
      unsafe.monitorExit(record);
    }
  } else {
      // could not lock record
  }
}

My advice would be to use this approach only if you cannot refactor your code to use java.util.concurrent Lock objects for this and if you are running on an Oracle VM.

I needed to also find a solution to this, so searched the Java Concurrency API and came across StampedLock. The project is using Java 8. I am working in a heavily-threaded asynchronous data service that communicates with a native library and contains long-living configuration objects, necessitating sometimes-complex concurrency logic; thankfully this turned out to be relatively simple with the StampedLock class.

StampedLock has a method called tryOptimisticRead which does not wait, it just returns the status in the form of a long-time time stamp, where zero (0) indicates an exclusive lock is held. I then do delay for up to a second but you could just use the function without any sort of delay.

Here's how I'm detecting whether or not there's an exclusive lock, this paradigm is used in multiple locations and includes error handling:

    int delayCount = 0;

    //Makes sure that if there is data being written to this field at
    // this moment, wait until the operation is finished writing the
    // updated data.
    while (data1StampedLock.tryOptimisticRead() == 0)
    {
        try
        {
            delay(WRITE_LOCK_SHORT_DELAY);
            delayCount += 1;
        }
        catch (InterruptedException e)
        {
            logError("Interrupted while waiting for the write lock to be
                       released!", e);
            Thread.currentThread().interrupt();

            //There may be an issue with the JVM if this occurs, treat
            // it like we might crash and try to release the write lock.
            data1StampedLock.tryUnlockWrite();
            break;
        }

        if (delayCount * WRITE_LOCK_SHORT_DELAY > TimeUnit.SECONDS.toMillis(1))
        {
            logWarningWithAlert("Something is holding a write lock on" +
                " the data for a very, very long time (>1s). This may" +
                " indicate a problem that could cause cascading" +
                " problems in the near future." +
                " Also, the value for the data that is about to be" +
                " retrieved could potentially be invalid.");
            break;
        }
    }

    long nonExclusiveLockStamp = data1StampedLock.readLock();
    Data data1NonVolatile = data1;
    data1StampedLock.unlockRead(nonExclusiveLockStamp);
    
    return data1NonVolatile;

The read locks on a StampedLock are non-exclusive and are like reading from a thread-safe Map or HashTable, where it is multi-read/single-write.

Here is how I am using the exclusive lock to communicate to other threads that the instance data is being written to:

    long d1LockStamp = data1StampedLock.writeLock();
    this.data1 = data1;
    data1StampedLock.unlockWrite(d1LockStamp);

So if you wanted to only check whether or not something is locked at any given moment, you need only something simple like the following statement to get the status:

    boolean data1IsLocked = data1StampedLock.tryOptimisticRead() == 0;

Then check the value of that boolean.

There are, of course, the caveats and Here Be Dragons information mentioned in other answers (namely that the information is immediately stale), but if you really need to lock something and check that lock from another thread, this seemed to me to be the most reasonable, safe, and effective way that uses the java.util.concurrency package with no external dependencies.

Related