I hope you have gone through my previous post Inter-Thread Communications in Java before reading it.
We have a classic 'Producer-Consumer' problem to explain the use of Inter-Thread communications in java,where one thread is producing some data and another is consuming it .The producer has to wait until the consumer is finished before it generates more data.
Let us start with an incorrect implementation of Producer-Consumer problem, where we are just using the mercy of synchronized method .
// An incorrect implementation of a producer and consumer.
class Q {
int n;
synchronized int get() {
System.out.println("Got: " + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println("Put: " + n);
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(++i);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}
public class Main {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
}
}
Although the put( ) and get( ) methods on Q are synchronized, nothing stops the producer from producing more things than the consumer can use and nothing stops the consumer from using the same product more than once.
Thus,you will get a wrong output as shown below.
Put: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Got: 6
Here Consumer used the same product, product-1 thrice and Consumer didn't get a chance to use the products, Product-2,Product-3,Product-4 and Product-5.
Now we came to know that ,with the only use of synchronized, can't do proper communication between multiple threads.Don't worry ,java is rich enough to provide a solution for this issue,providing three methods ,wait(),notify() and notifyAll() for Inter-Thread Communication.
Let us try the Producer-Consumer problem with the wait() and notify() methods.
// A correct implementation of a producer and consumer.
class Q {
int n;
boolean isQueueEmpty = true;
synchronized int get() {
if (isQueueEmpty)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
isQueueEmpty = true;
notify();
return n;
}
synchronized void put(int n) {
if (!isQueueEmpty)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
isQueueEmpty = false;
System.out.println("Put: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}
public class Main {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
}
}
Here you will get the expected result as shown below
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5
We can see that Producer is not overrunning the Consumer and the Consumer is not using the same product twice.
Summary
We can conclude the above example as given below
We have a classic 'Producer-Consumer' problem to explain the use of Inter-Thread communications in java,where one thread is producing some data and another is consuming it .The producer has to wait until the consumer is finished before it generates more data.
Let us start with an incorrect implementation of Producer-Consumer problem, where we are just using the mercy of synchronized method .
// An incorrect implementation of a producer and consumer.
class Q {
int n;
synchronized int get() {
System.out.println("Got: " + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println("Put: " + n);
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(++i);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}
public class Main {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
}
}
Although the put( ) and get( ) methods on Q are synchronized, nothing stops the producer from producing more things than the consumer can use and nothing stops the consumer from using the same product more than once.
Thus,you will get a wrong output as shown below.
Put: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Got: 6
Here Consumer used the same product, product-1 thrice and Consumer didn't get a chance to use the products, Product-2,Product-3,Product-4 and Product-5.
Now we came to know that ,with the only use of synchronized, can't do proper communication between multiple threads.Don't worry ,java is rich enough to provide a solution for this issue,providing three methods ,wait(),notify() and notifyAll() for Inter-Thread Communication.
Let us try the Producer-Consumer problem with the wait() and notify() methods.
// A correct implementation of a producer and consumer.
class Q {
int n;
boolean isQueueEmpty = true;
synchronized int get() {
if (isQueueEmpty)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
isQueueEmpty = true;
notify();
return n;
}
synchronized void put(int n) {
if (!isQueueEmpty)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
isQueueEmpty = false;
System.out.println("Put: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}
public class Main {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
}
}
Here you will get the expected result as shown below
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5
We can see that Producer is not overrunning the Consumer and the Consumer is not using the same product twice.
Summary
We can conclude the above example as given below
PRODUCER | CONSUMER | NOTE | |
STEP 1 | Producing.... | Waiting for lock | Producer got lock on Queue |
STEP 2 | Produced one product and invoked wait() | Producer released lock on Queue | |
STEP 3 | Waiting for lock | Consuming.... | Consumer got lock on Queue |
STEP 4 | Consumed one product and invoked notify() | Consumer released lock on Queue |
No comments:
Post a Comment