Synchronization in Java
Synchronization in Java is the way to control access to shared data when several threads use it, so that only one thread at a time can run a piece of code that reads or changes that data. Without it, threads can interfere with each other and produce wrong results. Java's basic tool is the synchronized keyword, which uses a lock (also called a monitor) that every object has. In this lesson, you will learn why synchronization is needed, how locks work, and the tools covered in this chapter.
Why Synchronization Is Needed
Threads share the heap. If two threads change the same variable at the same moment, their changes can overlap and one can be lost. This is called a race condition (see "Race Conditions and Thread Safety"). Synchronization prevents it by making a block of code mutually exclusive: while one thread is inside, others must wait.
How a Monitor Works
- Every Java object has one intrinsic lock (monitor).
- A thread acquires the lock when it enters a
synchronizedmethod or block, and releases it when it leaves, even if an exception is thrown. - While one thread holds the lock, other threads trying to enter code synchronized on the same object are
BLOCKED. - Releasing the lock also makes the thread's changes visible to the next thread that takes it.
Example: Fixing a Shared Counter
Let's see an example where two threads add to a counter, first without and then with synchronization. The unsynchronized result changes from run to run, so the output is a sample.
public class SyncCounter {
static int unsafeCount = 0;
static int safeCount = 0;
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable work = () -> {
for (int i = 0; i < 100_000; i++) {
unsafeCount++;
synchronized (lock) {
safeCount++;
}
}
};
Thread a = new Thread(work);
Thread b = new Thread(work);
a.start();
b.start();
a.join();
b.join();
System.out.println("Without synchronization: " + unsafeCount);
System.out.println("With synchronization: " + safeCount);
}
}Without synchronization: 199944
With synchronization: 200000Explanation: Each thread adds 100,000 to both counters, so both should end at 200,000. unsafeCount++ is really three steps (read, add, write), and the threads' steps overlap, so some updates are lost. (How many varies: in three runs on one machine, 56, 57 and 39 updates were lost.) safeCount++ runs inside synchronized (lock), so only one thread changes it at a time and no update is lost.
Tools for Synchronization
| Tool | Purpose | Lesson |
|---|---|---|
synchronized methods | Lock the whole method | "Synchronized Methods in Java" |
synchronized blocks | Lock only part of a method | "Synchronized Block in Java" |
wait, notify, notifyAll | Let threads signal each other | "Inter-Thread Communication in Java" |
volatile | Make a variable's changes visible | "Volatile Keyword in Java" |
java.util.concurrent locks, atomics, collections | More flexible tools | Covered later in the course |
Common Mistakes
Synchronizing on different objects. Threads using different locks don't exclude each other.
Synchronizing only the writes. Reads of shared data need the same lock, or they may see stale values.
Holding a lock while doing slow work such as I/O. Other threads wait unnecessarily.
Interview Corner
What is synchronization in Java?
Synchronization is the mechanism for controlling concurrent access to shared state. With the synchronized keyword, a thread must acquire an object's intrinsic lock (monitor) before executing the guarded method or block, so only one thread at a time can run code guarded by that lock. Releasing the lock on exit, normally or by exception, also publishes the thread's writes to the next thread that acquires the same lock, preventing both race conditions and visibility problems.
Practice
- Easy: Run
SyncCounterfive times and write down the unsafe results. - Harder: Change the example to use four threads and check that the synchronized counter is always 400,000.
Key Takeaways
Was this tutorial helpful?