Fundamentals 20 min read

Why Your Java Producer‑Consumer Code Deadlocks and How to Fix It

This article explains the root causes of thread‑safety problems in Java producer‑consumer scenarios, demonstrates how race conditions and deadlocks arise, and provides step‑by‑step solutions using synchronized blocks, wait/notify, notifyAll, and explicit Lock with Condition objects.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
Why Your Java Producer‑Consumer Code Deadlocks and How to Fix It

Introduction

Thread safety in Java requires careful coordination when multiple threads share mutable objects. The classic producer‑consumer pattern illustrates the pitfalls of unsynchronised access and the mechanisms ( synchronized, wait / notify, Lock / Condition) that can be used to guarantee correct behaviour.

1. Producer‑Consumer Race Conditions

1.1 Problem illustration

A shared Resource object holds id, name and wheelNumber. Two threads continuously write and read this object:

public class Resource {
    private int id;
    private String name;
    private int wheelNumber;
    private boolean flag = false; // used for coordination
    // getters, setters, toString omitted
}

public class Input implements Runnable {
    private final Resource r;
    public Input(Resource r) { this.r = r; }
    public void run() {
        for (int i = 0; ; i++) {
            if (i % 2 == 0) {
                r.setId(i);
                r.setName("小汽车");
                r.setWheelNumber(4);
            } else {
                r.setId(i);
                r.setName("电动车");
                r.setWheelNumber(2);
            }
        }
    }
}

public class Output implements Runnable {
    private final Resource r;
    public Output(Resource r) { this.r = r; }
    public void run() {
        for (;;) {
            System.out.println(r.toString());
        }
    }
}

Without coordination the producer may be pre‑empted after setting some fields, allowing the consumer to read a partially updated object. This leads to missing wheels or duplicated consumption.

1.2 Solutions

Synchronized block – guard the whole production and consumption steps with a lock on the shared object:

synchronized (r) {
    // set all fields atomically
}

wait/notify – introduce a flag and make the producer wait while the flag is true, the consumer wait while the flag is false. Use a while loop to protect against spurious wake‑ups:

if (r.isFlag()) {
    r.wait();
}
// produce
r.setFlag(true);
r.notify();

notifyAll – replace notify() with notifyAll() when multiple producers or consumers exist, ensuring that any waiting thread can be awakened.

2. Deadlock and Explicit Locks

2.1 Classic deadlock example

Two threads acquire locks in opposite order, causing a circular wait:

class Deadlock1 implements Runnable {
    private final Object lock1;
    private final Object lock2;
    public Deadlock1(Object o1, Object o2) { lock1 = o1; lock2 = o2; }
    public void run() {
        while (true) {
            synchronized (lock1) {
                System.out.println("Deadlock1----lock1");
                synchronized (lock2) {
                    System.out.println("Deadlock1----lock2");
                }
            }
        }
    }
}

class Deadlock2 implements Runnable {
    private final Object lock1;
    private final Object lock2;
    public Deadlock2(Object o1, Object o2) { lock1 = o1; lock2 = o2; }
    public void run() {
        while (true) {
            synchronized (lock2) {
                System.out.println("Deadlock2----lock2");
                synchronized (lock1) {
                    System.out.println("Deadlock2----lock1");
                }
            }
        }
    }
}

Both threads block after printing the first two lines, demonstrating a deadlock.

2.2 Avoiding deadlock in multi‑producer‑consumer

When several producers and consumers share the same monitor, using if to test the flag can wake a thread of the same type, leaving the opposite side waiting. Replacing the test with a while loop and using notifyAll() guarantees that a waiting thread of the correct role is awakened.

2.3 Using java.util.concurrent.locks.Lock

Java 1.5 introduced explicit locks with separate Condition objects, allowing distinct wait‑sets for producers and consumers.

Lock lock = new ReentrantLock();
Condition inCond = lock.newCondition();   // producer monitor
Condition outCond = lock.newCondition();  // consumer monitor

Producer logic:

lock.lock();
while (r.isFlag()) {
    inCond.await();
}
// produce the resource
r.setId(i);
... // set other fields
r.setFlag(true);
outCond.signal();
lock.unlock();

Consumer logic:

lock.lock();
while (!r.isFlag()) {
    outCond.await();
}
System.out.println(r);
r.setFlag(false);
inCond.signal();
lock.unlock();

Full example:

public static void main(String[] args) {
    Resource r = new Resource();
    Lock lock = new ReentrantLock();
    Condition inCond = lock.newCondition();
    Condition outCond = lock.newCondition();
    Input producer = new Input(r, lock, inCond, outCond);
    Output consumer = new Output(r, lock, inCond, outCond);
    new Thread(producer).start();
    new Thread(producer).start();
    new Thread(consumer).start();
    new Thread(consumer).start();
}

This design eliminates the race conditions of the naïve version and prevents deadlock by ensuring that only the appropriate side is signalled.

Conclusion

Multithreading improves CPU utilisation but introduces safety challenges when mutable state is shared. synchronized combined with wait / notify (or notifyAll) can coordinate a single producer‑consumer pair.

Explicit Lock and Condition provide finer‑grained control, separate wait‑sets, and avoid the limitations of intrinsic locks in more complex scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaconcurrencydeadlockthread safetyLockProducer Consumersynchronized
IT Architects Alliance
Written by

IT Architects Alliance

Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.