When I first began working with threads in Vala, I was happy to see its lock keyword. Compare that response to when I wrote my first Java program (wa-aa-ay back in 1996). I was a bit put off by the synchronized keyword — I still had in me that C-inspired purity that there should be a strict separation between language and library. I was blind to the idea that critical sections and semaphores should be built into a “serious” language. I’m sure I muttered something about syntactic sugar and sighed and gave Java threads a try … and loved them. Critical sections and block-scoped programming really do go hand-in-hand. It was a joy to know that I could never leave a critical section locked when I exited a function. That and Java’s intelligent exception handling really sold me on the language.
But when I began coding threads in Vala, I noticed some limitations to the lock keyword. Firstly (and oddly), the compiler would only allow me to lock member variables of the current object, but not the object itself. This is perfectly legal in Java:
public void method() {
synchronized (this) {
// protected code
}
}
This is so useful, Java has syntax for locking this throughout an entire method:
public synchronized void method() {
// protected code
}
So why can’t Vala do something similar? Why can’t I lock(this) — especially since, if the this variable is a member of another object, that object can lock it!
And I noticed something even odder with lock. When you synchronize on an object in Java, you’re essentially using an invisible lock() method on the object. (This is the “intrinsic lock” or “monitor lock” in Java-speak.) Beneath the covers it’s something like this:
object.lock();
try {
// protected code
} finally {
object.unlock();
}
You can see why this will compile in Java but produce its ill-named NullPointerException:
Object object = null;
synchronized (object) {
// ka-boom
}
However, this code works just fine in Vala:
class Foo {
Object object = null;
public void go() {
lock (object) {
// smooth sailing
}
}
}
What’s the story? If object is null, how can it be locked?
It turns out that Team Vala have come up with an interesting paradigm for locking variables that (a) works around certain limitations of GObject, and (b) actually cleans up some of my issues with Java-style critical sections.
The key to understanding the lock operator in Vala is to understand that you’re not locking the variable itself. You’re locking the memory used to store that variable. That’s why Vala can only lock member variables of a class. The only other storage to lock is the stack, which is rarely (if ever) useful (and probably indicates a design flaw), or global memory, which isn’t such a hot idea either. (Locking a static member of a class is allowed.) Jürg discussed this on the Vala mailing list.
Like Java, lock is calling a “magic” reentrant mutex that’s associated with the member variable. This mutex cannot be reliably passed around with the variable (which would be necessary for code in another class to lock it). And GObject doesn’t provide a built-in object-wide mutex that Vala can use. Vala could magically generate one in your own classes, but lock wouldn’t work on any non-Vala generated GObject — including GTK, GLib, and so on. Hence, this rule is also a nod to the limitations of the libraries Vala is building upon. To put it another way, lock always works because it can only lock Vala-produced objects. Ta-da.
And you can lock native types, not just objects. Locking an int counter is a snap in Vala without resorting to Java’s coarse-grained synchronized(this) or a dummy Object.
Another advantage to Vala’s approach is that it solves one of my gripes of Java synchronization. Going back to the analogy I mentioned earlier, every Object in Java has an invisible lock() and unlock() method — but those methods are public. Anybody can lock an object and hold that lock during their six-deep nested loops or blocking calls, even if that lock is relied upon elsewhere in the system (by, say, a thread that very object created and maintains).
Vala doesn’t have this problem. A member variable is only lockable by methods of its class. Problem solved.
And go back to the other example, where Java will throw a NullPointerException when synchronizing on a null object. Because Vala is locking the storage of the reference, and not the reference itself, you can do this reliably and easily:
// atomic singleton creation
lock (object) {
if (object == null)
object = new Object();
}
// atomic replacement of multiple fields
lock (object) {
object = new_object;
object_modifier = new_object_modifier;
}
You can do the second one in Java, but it’s probably not what you want — most Java programmers will synchronize on this, which may be too coarse. The only alternative is to generate a dummy Object to lock upon, which is kind of ugly. Nice and clean in Valaworld.