Proper Usage of wait and notify in Java to Avoid IllegalMonitorStateException

Nov 26, 2025 · Programming · 9 views · 7.8

Keywords: Java Multithreading | wait and notify | IllegalMonitorStateException | Synchronization Mechanism | Matrix Multiplication

Abstract: This article provides an in-depth exploration of the correct usage of wait and notify methods in Java multithreading programming. Through a matrix multiplication case study, it analyzes the causes of IllegalMonitorStateException and presents comprehensive solutions. Starting from synchronization mechanism principles, the article explains object monitor lock acquisition and release mechanisms, offers complete code refactoring examples, and discusses strategies for choosing between notify and notifyAll. Combined with system design practices, it emphasizes the importance of thread coordination in complex computational scenarios.

Problem Background and Exception Analysis

In the multithreaded matrix multiplication scenario, developers aim to implement ordered printing of computation results. In the original code, multiplication threads call the notify() method after completing cell calculations, while the printing thread waits for notifications via the wait() method. However, this implementation frequently throws IllegalMonitorStateException.

The root cause of this exception is that the thread calling notify() must hold the monitor lock of the target object. In the original code, the multiplication thread's notify() call lacks necessary synchronization block protection, while the printing thread uses synchronized (this) but both threads actually operate on different object instances.

Core Principles of Synchronization Mechanism

Java's wait() and notify() methods implement inter-thread communication based on object intrinsic lock mechanisms. Each Java object is associated with a monitor lock, and a thread must acquire this lock to call the object's wait(), notify(), or notifyAll() methods.

The correct usage pattern requires:

Code Refactoring and Implementation Solution

Based on the above principles, we refactor the thread coordination logic for matrix multiplication. First, define shared synchronization objects:

// Shared synchronization object
private final Object lock = new Object();

// Counters for tracking printing order
private int currentRow = 0;
private int currentCol = 0;

Improved implementation of multiplication thread:

public void run() {
    int countNumOfActions = 0;
    int maxActions = randomize();
    
    for (int i = 0; i < size; i++) {
        result[rowNum][colNum] = result[rowNum][colNum] + row[i] * col[i];
        countNumOfActions++;
        
        if (countNumOfActions >= maxActions) {
            countNumOfActions = 0;
            maxActions = randomize();
            Thread.yield();
        }
    }
    
    isFinished[rowNum][colNum] = true;
    
    // Call notify within synchronization block
    synchronized (lock) {
        lock.notifyAll();
    }
}

Improved implementation of printing thread:

public void run() {
    System.out.println("The result matrix of the multiplication is:");
    
    while (currentRow < creator.getmThreads().length) {
        synchronized (lock) {
            try {
                // Use loop to check condition, avoiding spurious wakeups
                while (!creator.getmThreads()[currentRow][currentCol].getIsFinished()[currentRow][currentCol]) {
                    lock.wait();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
        
        // Print current cell
        System.out.print(creator.getResult()[currentRow][currentCol] + " ");
        
        // Update printing position
        if (currentCol < creator.getmThreads()[currentRow].length - 1) {
            currentCol++;
        } else {
            System.out.println();
            currentCol = 0;
            currentRow++;
        }
    }
}

Selection Strategy Between notify and notifyAll

In the original problem, using notify() may cause thread starvation issues. Since multiple multiplication threads may complete calculations simultaneously but there's only one printing thread, using notify() can only wake up one waiting thread. If the awakened thread is not the printing thread but another multiplication thread, the printing thread might never be awakened.

Therefore, we choose to use notifyAll(), which wakes up all threads waiting on that object. Although this incurs some performance overhead, it ensures system correctness. After being awakened, the printing thread checks whether the condition is met, and continues waiting if not.

System Design Considerations

In complex multithreaded systems, the design of inter-thread coordination mechanisms is crucial. The matrix multiplication case demonstrates a variant of the producer-consumer pattern, where multiplication threads act as producers and the printing thread acts as a consumer.

From a system design perspective:

Through practice with over 120 system design problems, developers can deeply understand multiple solutions to such coordination problems, including using advanced synchronization tools from the java.util.concurrent package, such as CountDownLatch, CyclicBarrier, or Phaser, which provide richer and safer thread coordination mechanisms.

Summary and Best Practices

Proper usage of wait() and notify() methods requires following several key principles: always call these methods within synchronization blocks, use the same synchronization object, check waiting conditions in loops to avoid spurious wakeups, and carefully choose between notify() and notifyAll().

For modern Java development, it's recommended to prioritize advanced concurrency tools provided by the java.util.concurrent package, which offer significant advantages in usability and safety. However, understanding the underlying wait() and notify() mechanisms remains crucial for deeply mastering Java concurrent programming.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.