views:

617

answers:

3

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,

+5  A: 

Take a look at the Lock objects introduced in the Java 5 concurrency packages.

e.g.

Lock lock = new ReentrantLock()
if (lock.tryLock()) {
   try {
      // do stuff using the lock...
   }
   finally {
      lock.unlock();
   }
}
   ...

The ReentrantLock object is essentially doing the same thing as the traditional synchronized mechanism, but with more functionality.

EDIT: As Jon has noted, the isLocked() method tells you at that instant, and thereafter that information is out of date. The tryLock() method will give more reliable operation (note you can use this with a timeout as well)

EDIT #2: Example now includes tryLock()/unlock() for clarity.

Brian Agnew
+11  A: 

One thing to note is that the instant you receive such information, it's stale. In other words, you could be told that no-one has the lock, but then when you try to acquire it, you block because another thread took out the lock between the check and you trying to acquire it.

Brian is right to point at Lock, but I think what you really want is its tryLock method:

if (lock.tryLock())
{
    // Got the lock, process record
}
else
{
    // Someone else had the lock, abort
}

You can also call tryLock with an amount of time to wait - so you could try to acquire it for a tenth of a second, then abort if you can't get it (for example).

(I think it's a pity that the Java API doesn't - as far as I'm aware - provide the same functionality for the "built-in" locking, as the Monitor class does in .NET. Then again, there are plenty of other things I dislike in both platforms when it comes to threading - every object potentially having a monitor, for example!)

Jon Skeet
Yes. That's a good point. I took the example code literally, whereas the above is definitely a more robust implementation
Brian Agnew
But how do I use a lock per record? Currently the records are stored in a HashTable of records ... so I need a matching Hashtable of Locks?I am trying to ensure I have the most possible concurrency, so if a process wants to access recordC that should be fine (if only recordB is locked) - I use a global LOCK then it is essentially the same as locking the entire hashtable.... that make any sense?
Shaitan00
@Shaitan00: The easiest way would be to have a lock within the record. Basically you want one lock associated with each record - so put it in the object.
Jon Skeet
Shaitan00
You do need to manage unlocking. See the tutorial I referenced in my answer and the unlock() method in the finally{} block
Brian Agnew
From the sample provided by Jon there is no try/catch/finally - and from reading the tryLock() the lock is aquired automatically if it returns true. So ... how do I manage the unlocking? if (lock.tryLock() { do the work lock.unlock(); } else { throw exception locked }
Shaitan00
@Shaitan00: If you follow the link there's more example code. You put the try/finally inside the `if (lock.tryLock())` basically.
Jon Skeet
A: 

While the Lock answers are very good, I thought I'd post an alternative using a different data structure. Essentially, your various threads want to know which records are locked and which aren't. One way to do this is to keep track of the locked records and make sure that data structure has the right atomic operations for adding records to the locked set.

I will use CopyOnWriteArrayList as an example because it's less "magic" for illustration. CopyOnWriteArraySet is a more appropriate structure. If you have lots and lots of records locked at the same time on average then there may be performance implications with these implementations. A properly synchronized HashSet would work too and locks are brief.

Basically, usage code would look like this:

CopyOnWriteArrayList<Record> lockedRecords = ....
...
if (!lockedRecords.addIfAbsent(record))
    return; // didn't get the lock, record is already locked

try {
    // Do the record stuff
}        
finally {
    lockedRecords.remove(record);
}

It keeps you from having to manage a lock per record and provides a single place should clearing all locks be necessary for some reason. On the other hand, if you ever have more than a handful of records then a real HashSet with synchronization may do better since the add/remove look-ups will be O(n) instead of linear.

Just a different way of looking at things. Just depends on what your actual threading requirements are. Personally, I would use a Collections.synchronizedSet( new HashSet() ) because it will be really fast... the only implication is that threads may yield when they otherwise wouldn't have.

PSpeed