Synchronization under the hood, and why Java 5 improves it
On the previous page, we saw that the synchronized keyword
provides some benefits, including simple implementation for the programmer without
getting bogged down in how the JVM actually implements synchronization. But we saw
that this "black box" approach has some potential disadvantages. To understand when
and how Java 5 improves this situation, we need to delve "under the hood" of
synchronization for a minute.
Let's consider the following program, which maintains a thread-safe counter:
public class Counter {
private int count;
public synchronized int getCount() { return count; }
public synchronized void incrementCount() { count++; }
}
Now, let's consider what a JVM implementation might have to do under the hood
when we enter one of the synchronized methods such as incrementCount().
Recall that entering and exiting a synchronized block actually means
(among other things) acquiring and releasing a lock on the
object being synchronized on (in this case, the Counter instance that
the method is being called on).
For every Java object, the JVM must therefore hold information at least on which thread (if any) currently
has access to the lock and, how many times that thread has acquired the lock. We'll see
in a moment that it in some cases it may need to hold a pointer to an operating system
lock object plus potentially other information. The JVM needs to:
- check the variable telling it which thread owns the lock on the object;
- if no thread owns the lock, mark the variable as being the current thread's ID, and
set the lock count to one;
- if the current thread owns the lock, increase the lock count;
- if a different thread owns the lock, we can't proceed for now and need to
somehow wait for the lock to become available. Many operating systems provide a
"native" means to wait for other threads via low-level lock objects, so a possible
implementation is to create/wait for one of these.
All of the above actions must occur atomically. It's no good if we
read the thread owner and see that no thread owns the lock, but before we have chance
to set our thread ID and update the lock count, another thread intervenes, reads the
zero thread owner, and also thinks it can take the lock. So at a very low level, we need
to synchronize on this "lock housekeeping" data.
On the next page, we look at this low-level
means of synchronization.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.