Core Java

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.

Lukas Eder

Lukas is a Java and SQL enthusiast developer. He created the Data Geekery GmbH. He is the creator of jOOQ, a comprehensive SQL library for Java, and he is blogging mostly about these three topics: Java, SQL and jOOQ.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button