Java Intermediate

0% completed

Previous
Next
Basics of Thread Synchronization

In a multithreaded application, threads often share common resources. Without proper control, simultaneous access can result in race conditions, where the outcome depends on the sequence or timing of the threads' execution.

Why Is Thread Synchronization Necessary?

Concurrent Access Issues

When two or more threads access the same mutable data without coordination, the program can produce incorrect results. This is because thread operations can interleave in unpredictable ways (so-called race conditions). For example, consider two threads updating a shared counter: one increments (c++) while another decrements (c--). Even though c++ looks like a single operation, under the hood it consists of multiple steps: read the value, add 1, and write it back. If interleaved, one thread's update can overwrite the other’s update, leading to a lost increment or decrement. In short, unsynchronized access to shared data can break the correctness of our program.

Memory Visibility

Another issue is memory consistency. Without synchronization, changes made by one thread may not be visible to other threads promptly. Java’s memory model allows each thread to cache variables in CPU registers or local memory. If one thread updates a shared variable and another thread reads it without synchronization, the reader might see an old value (stale data). Proper synchronization ensures a happens-before relationship – meaning that writes by one thread happen-before subsequent reads by another, thus guaranteeing visibility of the latest values. In summary, synchronization is needed to prevent race conditions and to maintain a consistent view of memory across threads.

Real-World Consequences

Failing to synchronize threads can cause erratic behavior that’s hard to debug. For instance, imagine a banking application where two threads update an account balance: without synchronization, one deposit might be lost, resulting in money vanishing due to a race condition. Or consider a configuration flag updated by one thread and read by others – if not handled correctly, other threads might never see the updated flag and continue running with outdated information. Thus, thread synchronization is essential for building correct and reliable multithreaded applications.

Ways to Achieve Thread Synchronization in Java

Java’s synchronized keyword is used to ensure that only one thread at a time can access critical sections of code, protecting shared data from concurrent modifications. There are two main ways to use synchronization in Java:

  • Synchronized Methods: Synchronize the entire method.
  • Synchronized Blocks: Synchronize only a specific portion (block) of code, offering more granular control.

Before, we learn to achieve synchronization, let's look at the problem arising without synchronization.

Race Condition

This example demonstrates a race condition by having two threads increment a shared counter without synchronization. The final counter value may be less than expected due to concurrent access.

Java
Java

. . . .

Example Explanation:

  • Race Condition Scenario:
    • Two threads increment the shared counter 1000 times each.
    • The increment() method is not synchronized, so both threads may update the counter simultaneously.
  • Result:
    • Due to concurrent modifications, the final counter value is likely less than the expected 2000.
  • Takeaway:
    • Without synchronization, shared data can be modified inconsistently, leading to race conditions.

1. Achieving the Synchronization Using the Synchronized Methods

A synchronized method is declared with the synchronized keyword. When a thread calls a synchronized method, it must acquire the intrinsic lock (monitor) of the object before executing the method. This prevents other threads from entering any synchronized method on the same object until the lock is released.

Syntax

public synchronized void methodName() { // Critical section: code that accesses shared resources }
  • Explanation:
    Declaring a method as synchronized ensures that only one thread can execute it at a time for a given object instance.

Example: Synchronized Method to Increment a Shared Counter

In this example, we create a static synchronized method to increment a shared counter. Two threads will call this method concurrently, and synchronization ensures that the final counter value is consistent.

Java
Java

. . . .

Code Explanation:

  • Synchronized Method:
    • The increment() method is declared as synchronized, ensuring mutual exclusion on the shared counter.
  • Thread Execution:
    • Two threads each call the increment() method 1000 times concurrently.
  • Result:
    • Synchronization guarantees that the final counter value is 2000, preventing race conditions.

2. Synchronized Blocks

Synchronized blocks allow you to limit synchronization to a specific part of a method rather than synchronizing the entire method. This is useful for reducing overhead and increasing performance when only a small section of code (the critical section) requires exclusive access.

Syntax

synchronized(lock) { // Critical section: code that accesses shared resources }
  • Explanation:
    • The lock is an object that serves as a monitor.
    • Only the code within the block is synchronized, and a thread must acquire the lock before entering the block.

Example: Synchronized Block to Increment a Shared Counter

In this example, we use a synchronized block with a dedicated lock object to increment a shared counter. This ensures that only the critical section that modifies the counter is synchronized, improving efficiency compared to synchronizing the entire method.

Java
Java

. . . .

Code Explanation:

  • Synchronized Block Usage:
    • The increment() method uses a synchronized block, locking on a dedicated object lock.
  • Critical Section:
    • Only the code that increments the counter is synchronized, limiting the performance overhead.
  • Thread Execution:
    • Two threads each call the increment() method 1000 times concurrently.
  • Result:
    • The synchronized block ensures that the final counter value is 2000, demonstrating effective synchronization with finer control.

.....

.....

.....

Like the course? Get enrolled and start learning!
Previous
Next