Java Producer‑Consumer Example with Multiple Threads and Synchronization Improvements
This article explains the classic producer‑consumer problem in Java, demonstrates initial implementations using wait/notify with a single producer and consumer, analyzes issues that arise with multiple threads, and presents step‑by‑step code refinements—including while‑loops and notifyAll—to achieve correct multithreaded synchronization.
The article starts with a simple scenario of two threads, one producer and one consumer, where the producer creates a resource and the consumer consumes it, highlighting the need for synchronization to maintain data integrity.
It discusses the use of the Object methods wait() and notify() for thread coordination, explaining how a producer waits when the buffer is full and a consumer waits when it is empty.
Initial code implementation includes four classes: Resource.java , Producer.java , Consumer.java , and ProducerConsumerTest.java . The Resource class uses a boolean flag and synchronized methods with if checks, while the producer and consumer run in separate threads.
/**
* Created by yuandl on 2016-10-11.
*/
public class Resource {
private int number = 0;
private boolean flag = false;
public synchronized void create() {
if (flag) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
number++;
System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
flag = true;
notify();
}
public synchronized void destroy() {
if (!flag) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + "消费者****" + number);
flag = false;
notify();
}
}The test run prints alternating producer and consumer messages without errors, confirming the basic implementation works.
Next, the article expands the scenario to four threads (two producers, two consumers) and introduces notifyAll() to wake all waiting threads. However, the output reveals problems such as a single item being consumed twice or not consumed at all.
Root cause analysis shows that using if for flag checks allows threads to proceed without re‑checking the condition after being notified, leading to data inconsistencies.
The proposed solution replaces the if statements with while loops in the Resource methods, ensuring the condition is re‑evaluated after each wake‑up.
public synchronized void create() {
while (flag) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
number++;
System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
flag = true;
notify();
}
public synchronized void destroy() {
while (!flag) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + "消费者****" + number);
flag = false;
notify();
}Even with the while fix, a deadlock can occur because notify() may wake a thread of the same type, leaving the opposite side waiting. Switching to notifyAll() resolves this by waking all waiting threads.
public synchronized void create() {
while (flag) { /* wait */ }
// produce
flag = true;
notifyAll();
}
public synchronized void destroy() {
while (!flag) { /* wait */ }
// consume
flag = false;
notifyAll();
}Running the final version with notifyAll() and while loops produces correct alternating output for many iterations, demonstrating a robust solution to the multithreaded producer‑consumer problem.
At the end, the article includes a promotional note inviting readers to join a Java learning group and follow a public account for daily Java tips.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.