"What does synchronized mean?" — a question that haunts every other junior
\n\nIf you've ever written a multithreaded application in Java, you've likely encountered this situation: two threads simultaneously modify the same variable, resulting in bugs that reproduce once in a hundred runs. Sound familiar? This is exactly what the synchronized keyword is for. But what does it actually do? How does it work "under the hood"? And most importantly — when should you use it, and when is it better to avoid it?
In this article, we won't just paraphrase the documentation. We'll break down a popular Stack Overflow question (the one that gathered thousands of views), adapt it into an educational format, and show a comparison of approaches: synchronization at the method level, at the block level, and alternatives from java.util.concurrent. Let's go!
The Problem: Race Condition
\n\nImagine: you have a counter that two threads increment. The code is simple, but the result is unexpected.
\n\npublic class Counter {\n private int count = 0;\n\n public void increment() {\n count++;\n }\n\n public int getCount() {\n return count;\n }\n}\n\nWe run two threads, each performing 1000 increments. You'd expect 2000. But in reality — 1987, 1993, or even 1765. Why? Because the count++ operation is not atomic. It consists of three steps: read → increment → write. The threads "overlap," and one change gets lost.
This is a race condition. And synchronized is one of the main tools to combat it.
What does synchronized do? Let's look under the hood
\n\nThe synchronized keyword in Java guarantees mutual exclusion (mutex) and visibility of changes between threads. When a thread enters a synchronized block, it acquires the monitor of the object (or class). Other threads trying to enter any synchronized block on the same monitor are blocked until it is released.
Important: synchronized only works with one monitor. If you have two different objects — synchronization will not work.
Two levels of synchronization
\n- \n
- Synchronized method — locks the entire method, uses the object's monitor (
this). \n - Synchronized block — locks only part of the code, you can specify any object as the monitor. \n
Let's compare them in practice.
\n\nComparison of approaches: method vs block
\n\n1. Synchronized method
\n\nThe simplest way — add synchronized to the method signature.
public class SafeCounter {\n private int count = 0;\n\n public synchronized void increment() {\n count++;\n }\n\n public synchronized int getCount() {\n return count;\n }\n}\n\nPros:
\n- \n
- Minimal code, easy to read. \n
- Guarantees the entire method is an atomic operation. \n
Cons:
\n- \n
- "Heavy" locking — even if the method has only one line, the thread is blocked for the entire execution time. \n
- Reduces performance if the method is long or called frequently. \n
- Not flexible — you cannot synchronize only part of the logic. \n
2. Synchronized block
\n\nMore fine-grained control. You decide which object to use as the monitor.
\n\npublic class SafeCounterWithBlock {\n private int count = 0;\n private final Object lock = new Object();\n\n public void increment() {\n synchronized (lock) {\n count++;\n }\n }\n\n public int getCount() {\n synchronized (lock) {\n return count;\n }\n }\n}