Thread synchronization

Thread synchronization in Java is a mechanism that ensures two or more threads do not simultaneously access a shared resource, which could lead to inconsistent data or race conditions. Java provides several ways to handle thread synchronization.

Here’s a detailed explanation:


1. Why Synchronization is Needed

Consider two threads trying to update the same bank account balance:

class Account {
    int balance = 1000;

    void deposit(int amount) {
        balance += amount;
    }
}

If two threads call deposit() at the same time, the final balance may be incorrect due to race conditions.


2. Using synchronized Keyword

a) Synchronized Method

Marking a method as synchronized ensures that only one thread at a time can execute that method on the same object.

class Account {
    int balance = 1000;

    synchronized void deposit(int amount) {
        balance += amount;
    }

    synchronized void withdraw(int amount) {
        balance -= amount;
    }
}
  • Instance method: locks on the current object (this).
  • Static method: locks on the Class object.

b) Synchronized Block

You can synchronize only a part of the code to reduce overhead.

class Account {
    int balance = 1000;

    void deposit(int amount) {
        synchronized(this) {  // lock on the current object
            balance += amount;
        }
    }
}
  • Locks can also be on other objects for finer control: synchronized(lockObject) { // critical section }

3. Lock Objects (java.util.concurrent.locks)

Java provides more advanced locking mechanisms via Lock interface.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Account {
    int balance = 1000;
    Lock lock = new ReentrantLock();

    void deposit(int amount) {
        lock.lock(); // acquire lock
        try {
            balance += amount;
        } finally {
            lock.unlock(); // release lock
        }
    }
}

Advantages of Lock over synchronized:

  • Can try to acquire lock without blocking (tryLock()).
  • Can interrupt a thread waiting for a lock.
  • More flexible and scalable.

4. Volatile Keyword

volatile ensures visibility of changes across threads, but it does not provide atomicity.

class SharedResource {
    volatile int count = 0;

    void increment() {
        count++; // Not atomic, still need synchronization for correctness
    }
}

Use volatile when you only need to read/write a single variable and not perform compound operations.


5. Other Concurrency Utilities

Java’s java.util.concurrent package provides ready-made thread-safe structures:

  • AtomicInteger, AtomicLong – for atomic operations on integers.
  • ConcurrentHashMap – thread-safe map.
  • CopyOnWriteArrayList – thread-safe list.

Example:

import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    AtomicInteger count = new AtomicInteger(0);

    void increment() {
        count.incrementAndGet();
    }
}

✅ Key Points

  1. Synchronization prevents race conditions.
  2. synchronized ensures mutual exclusion.
  3. Synchronized methods lock on the object (or class if static).
  4. Synchronized blocks can lock on any object.
  5. Lock interface offers more flexibility.
  6. volatile guarantees visibility but not atomicity.
  7. Use java.util.concurrent utilities for high-performance concurrency.

Leave a Reply