Simple cross-thread locks
What to do when you wish to lock something in one thread, but unlock it in the other? Smells bad? No. Smells a bit unsafe? Yes. But how on earth it can be done if standard locks can be unlocked only from the threads they were locked at?
DISCLAIMER
This article is here for historical reasons. You shouldn’t be doing it as described here any more.
First of all, why do we need locks? We need them to make sharing of resources in multi-threaded environments as safe as possible. The simplest form of locking is synchronization. The more advanced version is locking with mutexes. By the way, Sun has good Mutex class hidden in sun.awt
package. I know, I know that it should be used with care as it isn’t a public part of API. Here’s a sample of how it could be used:
Mutex lock = new Mutex();
public void someMethod()
{
...
lock.lock();
try
{
... // some exclusive code
} finally
{
lock.unlock();
}
}
Fast forward to Java 1.5… Nowdays we have a good package java.util.concurrent.locks
with a big choice of different locks we could use instead of Mutex
class. All of them are well-thought and carfully implemented. Lock
and ReadWriteLock
are intended to help with “exclusive write, non-exclusive reads” scheme, their Reenterant
versions allow multiple entries to locks by the same thread, more to go…
The only problem is that sometimes you will need to lock object in one thread and unlock it in the other. Yes, I know that it sounds unsafe. But I also know that trying to do exactly the same with “safe” methods in some cases will lead to a very big overhead in code if even solution is possible.
There’s no need to search for example for too long. Everybody knows the rule that Swing code should run only in Events Dispatch Thread (EDT). Having this in mind, the only way to modify the model behind some tree or table component is to do it while being in EDT, right? While it sounds simple, it not necessarily so on practice. I had several occasions when there were different computations to be taken based on current model state in order to get a new state. The model should be locked exclusively to avoid race conditions. I couldn’t do computations being in EDT as it would lock the GUI for a while and I couldn’t run them out of EDT as the modifications should be followed by fireXYZ()
to notify Swing components about changes which should be called from within EDT. Not very nice dilema, right?
So, after a while of thinking and trying different approaches I realized that what I need is a lock which can be locked from one thread and unlocked from the other. I also realized that it isn’t safe, because if the thread which is supposed to unlock the resource terminates unexpectedly the resource will remain locked forever, but what are the options? The lock code is listed below. It makes no checks about which thread the resource was locked at when unlocking.
/**
* Simple lock with two states. It's possible to lock
* within one Thread and unlock it in any other.
*
* @author Aleksey Gureev (spyromus@noizeramp.com)
*/
public class SimpleLock
{
private boolean locked;
/**
* Creates lock.
*/
public SimpleLock()
{
locked = false;
}
/**
* Makes a try to do locking. If the object is
* already locked thread will wait for the unlocking.
* If there are several threads waiting for unlocking
* the fastest will establish locking when object will
* get unlocked.
*/
public synchronized void lock()
{
while (locked)
{
try
{
wait();
} catch (InterruptedException e)
{
}
}
locked = true;
}
/**
* Unlocks the object. Note that any
* thread can unlock the object. This is the feature
* of this simple lock.
*/
public synchronized void unlock()
{
locked = false;
notify();
}
/**
* Returns TRUE if this object is locked.
*
* @return TRUE if locked.
*/
public synchronized boolean isLocked()
{
return locked;
}
}
And here comes the sample of usage.
public class SomeClass
{
private SimpleLock lock = new SimpleLock();
/**
* This method is called in scope of some non-EDT thread and is
* intended to make some computations over the current model and
* update it with results firing. The update finishes with firing
* of required events from within EDT and consequent unlocking.
*/
public void actionMethod()
{
// Establish exclusive lock
lock.lock();
doSomeLengthyJob();
// Fire the results
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
fireIntervalAdded(this, ...);
fireIntervalRemoved(this, ...);
fireContentsChanged(this, startIndex, endIndex);
// Unlock the resource
lock.unlock();
}
});
}
}
I hope that you will have not very much use of this solution and it mostly showed in demonstration purposes only. By any means try to choose more safe way, but don’t forget about this one when you are in confusion.