I have the following simple code in which I put and take from a Queue represented as an ArrayList.
public class EmailService {
private Queue<Email> emailQueue;
private Object lock;
private volatile boolean run;
private Thread thread;
public void sendNotificationEmail(Email email) throws InterruptedException {
emailQueue.add(email);
synchronized (lock) {
lock.notify();
lock.wait();
}
}
public EmailService() {
lock = new Object();
emailQueue = new Queue<>();
run = true;
thread = new Thread(new Runnable() {
@Override
public void run() {
while (run) {
System.out.println("ruuuning");
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (emailQueue.getSize() > 0) {
sendEmail(emailQueue.poll());
}
lock.notify();
}
}
}
private void sendEmail(Email email) {
System.out.println("Sent email from " email.getFrom() " to " email.getTo() " with content: " email.getContent());
}
});
thread.start();
}
public void close() throws InterruptedException {
run = false;
synchronized (lock) {
lock.notify();
System.out.println("Thread will join " thread.isInterrupted());
thread.join();
System.out.println("Thread after join");
}
}
}
I don't understand why my thread is blocked in join()
method.
From main I call as follow:
eService = new EmailService();
Email e1 = new Email(client1, client2, "content1");
eService.sendNotificationEmail(e1);
eService.close();
CodePudding user response:
Without running it...
- The
close()
method holdslock
at the time it callsthread.join()
and waits onthread
(forever) thread
is waiting to reacquirelock
so cannot run
Both are now waiting on each other, this is a deadlock. Try moving the Thread.join()
after the synchronized
block:
public void close() throws InterruptedException {
run = false;
synchronized (lock) {
lock.notify();
System.out.println("Thread will join " thread.isInterrupted());
}
thread.join();
System.out.println("Thread after join");
}
CodePudding user response:
@drekbour explained how your program could hang in the join()
call, but FYI: Here's a different way that your program could hang. This is called lost notification.
Your main thread creates a new EmailService
instance. The new instance creates its thread and calls thread.start()
*BUT* it could take some time for the thread to actually start running. Meanwhile...
Your main thread creates a new Email
instance, and calls eService.sendNotificationEmail(...)
. That function adds the new message to the queue, locks the lock
, notifies the lock, and then waits on the lock.
Finally, The service thread starts up, enters its run()
method, locks the lock, and then it calls lock.wait()
.
At this point, the program will be stuck because each thread is waiting to be notified by the other.
The way to avoid lost notification is, in the consumer thread, do not call wait()
if the thing that you are waiting for already has happened.
synchronized(lock) {
while (theThingHasNotHappenedYet()) {
lock.wait();
}
dealWithTheThing();
}
In the producer thread:
synchronized(lock) {
makeTheThingHappen();
lock.notify();
}
Notice how both threads lock the lock. Ever wonder why lock.wait()
throws an exception if the lock isn't locked? The examples above illustrate why. The lock prevents the producer thread from making the thing happen after the consumer already has decided to wait. That is key: If the consumer were to wait after the producer calls notify()
then it's game over. The program hangs.