☕ Java Q19 / 105

Explain synchronized keyword in Java.

AI-Powered Answer ✓ Answered

`synchronized` is a crucial keyword in Java used for controlling access to shared resources by multiple threads, ensuring thread safety and preventing data corruption in concurrent programming environments. It achieves this by providing a mechanism for mutual exclusion.

What is `synchronized`?

The synchronized keyword in Java is primarily used to achieve thread safety. When multiple threads try to access and modify shared resources simultaneously, it can lead to inconsistent or corrupted data. synchronized provides a way to enforce mutual exclusion, meaning only one thread can access a synchronized block or method at any given time for a specific object, thus preventing race conditions.

How `synchronized` Works: Intrinsic Locks (Monitors)

Every object in Java has an associated monitor (also known as an intrinsic lock). When a thread enters a synchronized method or block, it attempts to acquire the monitor of the object on which the synchronization is performed. If the monitor is available, the thread acquires it and proceeds. If another thread already holds the monitor, the current thread blocks and waits until the monitor is released. Once the thread exits the synchronized block/method (either normally or due to an exception), the monitor is released.

Applying `synchronized`

1. Synchronized Methods

When synchronized is applied to an instance method, the lock acquired is the intrinsic lock of the current instance (this). All calls to synchronized instance methods on the same object will be mutually exclusive.

java
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

When synchronized is applied to a static method, the lock acquired is the intrinsic lock of the Class object itself (e.g., Counter.class). This means all calls to synchronized static methods for that class will be mutually exclusive across all instances.

java
class StaticCounter {
    private static int staticCount = 0;

    public static synchronized void incrementStatic() {
        staticCount++;
    }

    public static synchronized int getStaticCount() {
        return staticCount;
    }
}

2. Synchronized Blocks

Synchronized blocks provide more fine-grained control over locking. You can specify any object as the lock. This is useful when you only need to protect a small part of a method, or when you want to use a different lock object than this or the Class object.

java
class DataProcessor {
    private Object lock = new Object();
    private int data = 0;

    public void process() {
        // ... some non-critical operations ...
        synchronized (lock) {
            // Critical section: only one thread can execute this at a time
            data++;
        }
        // ... other non-critical operations ...
    }
}

Key Characteristics and Guarantees

  • Mutual Exclusion: Only one thread can execute a synchronized method or block on a given object at a time.
  • Visibility: When a thread releases a lock, all writes made by that thread are guaranteed to be visible to any other thread that subsequently acquires the same lock. This addresses the 'memory visibility problem'.
  • Atomicity: It ensures that operations within the synchronized block/method are performed as a single, indivisible unit.
  • Happens-Before Relationship: Entering a synchronized block/method establishes a happens-before relationship with any subsequent execution of a synchronized block/method on the same monitor.

Potential Issues: Deadlock

While synchronized is powerful, its misuse can lead to deadlocks. A deadlock occurs when two or more threads are blocked indefinitely, each waiting for the other to release a resource (lock) that it needs.

Best Practices

  • Synchronize on private final objects: This prevents other code from acquiring the lock for unrelated purposes, potentially causing deadlocks or performance issues.
  • Keep synchronized blocks small: Only protect the critical section of code that truly needs thread safety. Over-synchronization can lead to performance bottlenecks.
  • Avoid synchronizing on this or String literals: Synchronizing on this can expose your internal lock, allowing external code to block your object. Synchronizing on String literals can lead to unintended locking as Java interns String literals, meaning multiple seemingly different strings might refer to the same object.
  • Consider java.util.concurrent utilities: For more complex concurrency scenarios, classes like ReentrantLock, Semaphore, CountDownLatch, and atomic variables (e.g., AtomicInteger) from the java.util.concurrent package often provide more flexibility and better performance than synchronized.