The synchronized keyword in Java
Declaring a method as synchronized
To the programmer, declaring a method as synchronized is essentially
the same as wrapping the method body in a synchronized (this) block.
So the above Counter class could be written:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
If we declare a static method as synchronized, then the
lock is obtained on the corresponding Class object.
Generally, which alternative used in cases such as this is a matter of
style, although at the bytecode level there is actually a very slight difference1.
However, there are some potential problems with declaring methods as synchronized:
- You generally don't get "private" control of the lock: any code that can see the
class in question is free to synchronize on– or worse, release the lock on–
that object (of course, that's also true of explicitly synchronizing on this or
X.class).
- It's easier for somebody reading the code to "forget" which object is actually
being synchronized on (class or instance).
- Declaring a method as synchronized means that you are committing to your
class using a particular locking mechanism. If a more effective locking mechanism
comes along (as has happened in Java 5), you can't use it without changing the
definition of your API.
Generally speaking, it's usually better not to expose to outside classes
implementational details that you don't need to; declaring a method as
synchronized may well be doing just that.
Bloch and Gafter (2005) provide a fun example of the first problem, which I have adapted
here. The Processor class below is intended to process in a separate
thread jobs added via its addJob() method. The requestStopAndWaitToFinish()
method is designed to tell the thread to stop running and prevent further jobs from being
added. Both methods are synchronized, so that in theory a call to addJob() can't
be run while the stop request method is running and waiting for the thread to finish:
public class Processor extends Thread {
private volatile boolean stopRequested;
public synchronized void requestStopAndWaitToFinish() {
stopRequested = true;
join();
}
public synchronized void addJob(Object job) {
if (!stopRequested) {
// ... interesting code
}
}
public void run() {
while (!stopRequested) {
// .. process jobs using more interesting code
}
}
}
What's not obviously wrong with this code at first sight is that the call to
join() inside requestStopAndWaitToFinish() actually gives up
the lock– via a call to Object.wait()–
while waiting for the thread to finish, so that another thread
can step in and run addJob()! The moral of the story is that
you should always be very sure that nothing else is going to interfere with the
lock acquired by a synchronized method, or else declare a separate, private
Object to synchronize on. (But conversely, if you are sure that nothing
else will interfere, it may not desirable to create lots of other spurious
objects just for the purpose of synchronization.)
On the next page, we look at the
volatile keyword in Java,
which is to some extent a "lighter" version of synchronization.
Notes:
1. When synchronized
is used in a method declaration, it is marked as a flag on the method. The
compiler does not insert instructions to acquire and release the lock;
instead, the VM sees the flag on the method declaration and knows to "automatically" acquire
and release the lock on method entry and exit. Now, is it worth shaving two bytecodes
from a method? Usually not, but consider, for example, that the
Hotspot compiler uses the number of bytecodes in a method as a metric for deciding
whether or not to inline it.
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.