Codectionary / Developer documentation / Java

synchronized Keyword

The synchronized keyword prevents multiple threads from executing a block of code (or a method) at the same time on the same object, protecting shared, mutable data from race conditions. When one thread enters a synchronized block, it acquires a lock on the specified object; any other thread trying to enter a synchronized block on that same object must wait until the lock is released.

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.