Syntax
synchronized (lockObject) {\n // critical section\n}Examples
The Problem Without Synchronization
Multiple threads incrementing a shared counter can produce an incorrect result due to a race condition.
class Counter {
private int count = 0;
public void increment() { // NOT thread-safe
count++; // this is actually three steps: read, add, write
}
public int getCount() {
return count;
}
}
// Without synchronization, running this with multiple threads can produce
// a final count LESS than expected, because increments can be lost
// when two threads read the same value before either writes back.synchronized Method
Marking an entire method as synchronized, so only one thread can execute it at a time per object.
class Counter {
private int count = 0;
public synchronized void increment() { // now thread-safe
count++;
}
public synchronized int getCount() {
return count;
}
}
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) counter.increment();
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();synchronized Block
Synchronizing only the specific critical section, rather than an entire method, for finer-grained control.
class BankAccount {
private double balance = 100;
private final Object lock = new Object();
public void withdraw(double amount) {
synchronized (lock) { // only this block is protected
if (amount <= balance) {
balance -= amount;
}
}
// other, non-critical work can happen outside the lock
}
}Best practices
- Synchronize the smallest block of code necessary (a synchronized block) rather than an entire large method, to minimize how long threads wait on each other
- Always synchronize on a private, final lock object rather than "this" when possible, to prevent external code from accidentally locking on the same object
- Keep synchronized blocks free of slow operations (like network calls or file I/O) - holding a lock for a long time hurts overall concurrency
- Consider higher-level concurrency utilities from java.util.concurrent (like AtomicInteger or ConcurrentHashMap) instead of manual synchronized blocks when they fit your use case
At a glance
- Purpose
- General-purpose application development
- File extension
- .java
- Runs in
- Java Virtual Machine
- Usually used with
- JDK and Java libraries
Specifications & further reading
Related Java documentation
Creating Threads (Thread & Runnable)
A thread is an independent path of execution within a program, letting multiple tasks run seemingly simultaneously. Java offers two main ways to create a thread: extending the Thread class directly and overriding its run() method, or implementing the Runnable interface and passing it to a Thread - the Runnable approach is generally preferred, since Java doesn't support multiple inheritance and Runnable keeps your class free to extend something else.ExecutorService & Thread Pools
ExecutorService, from java.util.concurrent, manages a pool of reusable threads, letting you submit tasks without manually creating and managing individual Thread objects. This avoids the overhead of constantly creating new threads and gives you control over how many run concurrently. Executors provides convenient factory methods for common pool configurations, like a fixed-size pool or a single-threaded executor.Atomic Variables
Classes like AtomicInteger, AtomicLong, and AtomicBoolean, from java.util.concurrent.atomic, provide thread-safe operations on single variables without needing explicit synchronized blocks. They use low-level, lock-free CPU instructions (compare-and-swap) to guarantee that operations like incrementAndGet() are atomic - performed as a single, uninterruptible step - even when many threads use them concurrently.Thread Control: sleep, join, interrupt
Java provides several methods for coordinating and controlling thread execution. Thread.sleep() pauses the current thread for a specified duration. join() makes the calling thread wait until another thread finishes before continuing. interrupt() signals a thread that it should stop what it's doing, a cooperative mechanism the target thread must actively check for and respond to.
A thread is an independent path of execution within a program, letting multiple tasks run seemingly simultaneously. Java offers two main ways to create a thread: extending the Thread class directly and overriding its run() method, or implementing the Runnable interface and passing it to a Thread - the Runnable approach is generally preferred, since Java doesn't support multiple inheritance and Runnable keeps your class free to extend something else.ExecutorService & Thread Pools
ExecutorService, from java.util.concurrent, manages a pool of reusable threads, letting you submit tasks without manually creating and managing individual Thread objects. This avoids the overhead of constantly creating new threads and gives you control over how many run concurrently. Executors provides convenient factory methods for common pool configurations, like a fixed-size pool or a single-threaded executor.Atomic Variables
Classes like AtomicInteger, AtomicLong, and AtomicBoolean, from java.util.concurrent.atomic, provide thread-safe operations on single variables without needing explicit synchronized blocks. They use low-level, lock-free CPU instructions (compare-and-swap) to guarantee that operations like incrementAndGet() are atomic - performed as a single, uninterruptible step - even when many threads use them concurrently.Thread Control: sleep, join, interrupt
Java provides several methods for coordinating and controlling thread execution. Thread.sleep() pauses the current thread for a specified duration. join() makes the calling thread wait until another thread finishes before continuing. interrupt() signals a thread that it should stop what it's doing, a cooperative mechanism the target thread must actively check for and respond to.