I wrote this article for SitePoint’s Java channel, where you can find a lot of interesting articles about our programming language. Check it out!
The synchronized
keyword can be used to ensure that only one thread at a time executes a particular section of code. This is a simple way to prevent race conditions, which occur when several threads change shared data at the same time in a way that leads to incorrect results. With synchronized
either entire methods or selected blocks can be single-threaded.
This article requires basic knowledge of Java threads and race conditions.
Synchronization Basics
Let’s see how to use synchronized
on methods and, more fine-grained, on code blocks. I will also explain how it works.
Using The synchronized
Keyword
If we add a synchronized
modifier to a method, the JVM guarantees that only one thread can execute a method at the same time:
class MutableInteger {
private int value;
public synchronized void increment() {
value++;
}
public synchronized int getValue() {
return value;
}
}
If several threads try to execute a synchronized method simultaneously, only one will be able to do it. The others will be paused until the first one exits the method. (In some sense it works much like a traffic light, making sure concurrent executions don’t collide.) Because of this, threads that increment the value
counter do not overwrite each other’s results and every increment will be recorded.
To see that the synchronized
keyword works I create ten threads that each increment the same counter ten thousand times. I created a gist with the code – it is basically the same as in the article explaining race conditions. As expected, the result is always the same and always correct:
Result value: 100000
The synchronized
keyword only limits thread access to one object. If you have two different instances, two different threads can use them at the same time, and they won’t block each other:
MutableInteger integer1 = new MutableInteger();
MutableInteger integer2 = new MutableInteger();
// Threads are using different objects and don't
// interact with each other
Thread thread1 = new Thread(new IncrementingRunnable(integer1));
Thread thread2 = new Thread(new IncrementingRunnable(integer2));
How synchronized
Works
Synchronization has two forms: synchronized methods and synchronized statements. So far we’ve only encountered the first type, here’s the second:
class MutableInteger {
private int value;
private Object lock = new Object();
public void increment() {
// Only one thread can execute this block of code at any time
synchronized (lock) {
value++;
}
}
public int getValue() {
// Only one thread can execute this block of code at any time
synchronized (lock) {
return value;
}
}
}
When a thread enters a synchronized block, it attempts to acquire the lock that is associated with the object passed to the statement. (In Java, an object’s lock is often called its monitor.) If another thread is currently holding the lock, the current one will be paused until the lock is released. Otherwise the thread succeeds and enters the synchronized block.
When the thread finishes the synchronized block, it releases the lock, and another thread can acquire it. A lock is released when a thread leaves a synchronized block even if an exception is thrown.
By the way, using the synchronized
keyword on a method instead of in a block is just shorthand for using the object’s own lock for synchronization:
// this is equivalent to 'public synchronized void ...'
public void increment() {
synchronized (this) {
value++;
}
}
Maybe you are now wondering what lock is used if you synchronize a static method.
public static synchronized void foo() { ... }
In that case it is the class object itself that get’s locked on, so it is equivalent to the following:
public static void foo() {
synchronized (TheClassContainingThisMethod.class) {
...
}
}
Multiple Locks
So far we’ve seen that a class only uses a single lock object, but it is common to use multiple locks in one class. This allows two different threads to execute two different methods on a single object in parallel:
class TwoCounters {
private Object lock1 = new Object();
private Object lock2 = new Object();
private int counter1;
private int counter2;
public void incrementFirst() {
synchronized (lock1) {
counter1++;
}
}
public void incrementSecond() {
synchronized (lock2) {
counter2++;
}
}
}
Counting in a Real-World Java Application
Synchronization is commonly used in real-world Java applications and is a powerful tool, but I would not advise you to use it for aggregating values from different threads as we did here. Java provides a special class for this, called AtomicInteger
, as a part of its rich concurrency package. It has better performance than the MutableInteger
that we’ve implemented here and a much richer interface.
Conclusions
In this post, you’ve learned how to avoid race conditions with the synchronized
keyword, which guarantees that only one thread con execute a given block of code. It can be used on a method level, in which case it uses the object itself as a lock, or on a block level, in which case the lock object has to be specified. Any Java object can be used as a lock.
A similarly fundamental concurrency feature are the Object
methods wait
and notify
, which can be used to implement guarded blocks to coordinate actions between different threads. Beyond that, Java has the Lock
interface that allows implementing synchronization similarly to the synchronized
keyword but has more flexibility. If you want to use collections in multiple threads, have a look at synchronized wrappers and particularly concurrent collections.
Share this: