If Java Were Designed Today: The Synchronizable Interface
Java has come a long way. A very long way. And it carries with it all the “junk” from early day design decisions.
One thing that has been regretted time and again is the fact that every object (potentially) contains a monitor. This is hardly ever necessary and this flaw was corrected, finally, in Java 5, when new concurrency APIs were introduced, such as the java.util.concurrent.locks.Lock
and its subtypes. Since then, writing synchronized, concurrent code has become a lot easier than before when we only had the synchronized
keyword and the hard-to-understand wait()
and notify()
mechanism:
The synchronized modifier is hardly used anymore
The original language design specified for these “convenience” modifiers on methods:
// These are the same: public synchronized void method() { ... } public void method() { synchronized (this) { ... } } // So are these: public static synchronized void method() { ... } public static void method() { synchronized (ClassOfMethod.class) { ... } }
You hardly want to synchronize on the complete method scope, in order to keep synchronization time at a minimum, and factoring out a method every time you need synchronization is tedious.
Furthermore, the monitor breaks encapsulation. Everyone can synchronize on your monitor if you synchronize on this
or on the entire class
. You probably don’t want that, which is why most people who still do work with the synchronized
keyword will simply create an explicit, private lock object, such as:
class SomeClass { private Object LOCK = new Object(); public void method() { ... synchronized (LOCK) { ... } ... } }
If that’s the standard use-case for classic synchronized
blocks, do we then still need a monitor on every object?
Synchronized in a more modern Java version
If Java were designed with today’s knowledge about the Java language, we wouldn’t allow for using synchronized
on any random object (including strings or arrays):
// Wouldn't work synchronized ("abc") { ... }
We would introduce a special Synchronizable
marker interface, which guarantees that implementors will have a monitor. And the synchronized
block would only accept Synchronizable
arguments:
Synchronizable lock = ... synchronized (lock) { ... }
This would work exactly the same way as foreach or try-with-resources:
Iterable<Object> iterable = ... // The type to the right of ":" must be Iterable for (Object o : iterable) { ... } // The assignment type must be AutoCloseable try (AutoCloseable closeable = ...) { ... } // The assignment type must be a functional interface Runnable runnable = () -> {};
So, in order for a given language feature to work, the Java language imposes constraints on the types that are used in that context. In the case of foreach or try-with-resources, a concrete JDK type is required. In the case of lambda expressions, a matching structural type is required (which is rather esoteric but clever, for Java).
Unfortunately, for backwards-compatibility reasons, there will not be any new restriction added for synchronized
blocks. Or will there? It would be great, and an optional warning could be issued if the type is not Synchronizable
. This might allow, in the course of a couple of future major releases, to remove monitors from objects that are not really required to be synchronizable.
Which is essentially what the C language has been doing with mutexes all along. They’re a special thing. Not the common thing.
Reference: | If Java Were Designed Today: The Synchronizable Interface from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |