Thread Synchronization and Coordination

In Java, multithreading allows us to execute multiple threads concurrently. However, when multiple threads access shared resources simultaneously, conflicts may arise leading to inconsistent or incorrect results. To prevent such issues, thread synchronization and coordination techniques are essential.

Thread Synchronization

Thread synchronization ensures that multiple threads can access shared resources in an orderly and controlled manner. The synchronized keyword in Java is used to achieve synchronization. It can be applied to a method or a block of code.

Synchronized Methods

By using the synchronized keyword with a method, we ensure that only one thread can execute that method on a given object at a time. When a thread acquires the intrinsic lock (also known as a monitor) of an object, all other threads attempting to execute the synchronized method on the same object must wait until the lock is released.

public synchronized void synchronizedMethod() {
    // Code that needs to be synchronized
}

Synchronized Blocks

Synchronized blocks allow more fine-grained control over synchronization. Instead of synchronizing an entire method, we can synchronize a specific block of code. This is useful when only a part of the method requires synchronization.

public void unsynchronizedMethod() {
    // Code that doesn't need synchronization
    
    synchronized (this) {
        // Code that needs to be synchronized
    }
    
    // Code that doesn't need synchronization
}

Synchronized blocks also allow synchronization on different objects based on our requirements.

Intrinsic Locks and Reentrancy

Every Java object has an intrinsic lock, which is acquired when a thread enters a synchronized method or block. The intrinsic lock is released when the thread exits the synchronized portion of the code.

Java supports reentrant synchronization, which means that a thread trying to acquire an intrinsic lock it already holds will succeed. Reentrancy allows a thread to reuse existing locks, preventing deadlocks.

Thread Coordination

Thread coordination ensures that threads cooperate and synchronize their activities to achieve a desired outcome. Java provides several mechanisms for thread coordination.

Object Wait and Notify

The wait() and notify() methods, along with notifyAll(), are used for inter-thread communication and coordination. These methods allow a thread to pause its execution and release the lock, while another thread is signalled to wake up and acquire the lock.

  • wait()
    • Causes the current thread to wait until another thread invokes notify() or notifyAll() on the same object.
  • notify()
    • Wakes up one waiting thread that is waiting on the same object. The choice of the awakened thread is arbitrary.
  • notifyAll()
    • Wakes up all the threads that are waiting on the same object.

An important point to note is that wait(), notify(), and notifyAll() must be called from within a synchronized block or method, as they require the calling thread to hold the intrinsic lock.

synchronized (sharedObject) {
    // Code before wait()
    sharedObject.wait(); // Thread waits until another thread calls notify()/notifyAll()
    // Code after wait()
}

// In another thread
synchronized (sharedObject) {
    // Code before notify()/notifyAll()
    sharedObject.notify(); // Wakes up one waiting thread
    // sharedObject.notifyAll(); // Wakes up all waiting threads
    // Code after notify()/notifyAll()
}

Thread Join

The join() method is another way of thread coordination. It allows one thread to wait for the completion of another thread. When a thread calls join() on another thread, it halts until that thread terminates.

Thread thread1 = new Thread(() -> {
    // Code for thread1
});

Thread thread2 = new Thread(() -> {
    // Code for thread2
});

thread1.start();
thread2.start();

try {
    thread1.join(); // Wait for thread1 to complete
    thread2.join(); // Wait for thread2 to complete
} catch (InterruptedException e) {
    // Exception handling
}

Conclusion

Thread synchronization and coordination are fundamental concepts in concurrent programming. They allow us to manage shared resources and coordinate thread activities effectively. By understanding and correctly using synchronization techniques like synchronized methods and blocks, along with coordination mechanisms like wait(), notify(), notifyAll(), and join(), we can ensure thread safety and avoid race conditions or inconsistencies in our Java applications.


noob to master © copyleft