The

"Hi, Amigo!"

"I want to dive deep with you regarding wait-notify. The wait-notify methods provide a convenient mechanism for threads to interact. They can also be used to build complex high-level mechanisms for thread interaction."

"I'll start with a small example. Suppose we have a program for a server that must perform various tasks created by users through a website. Users can add various tasks at different times. The tasks are resource-intensive, but our server's 8-core processor can cope. How should we perform the tasks on the server?"

"First, we'll create a group of worker threads, as many as there are processor cores. Each thread will be able to run on its own core: The threads won't interfere with each other, and the processor cores won't sit idle."

"Second, we'll create a queue object where users' tasks will be added. Different types of tasks will correspond to different objects, but all of them will implement the Runnable interface so they can be run."

"Could you give me an example of a task object?"

"Check it out:"

A class that calculates n factorial when the run() method is called
class Factorial implements Runnable
{
 public int n = 0;
 public long result = 1;

 public Factorial (int n)
 {
  this.n = n;
 }

 public void run()
 {
  for (int i = 2; i <= n; i++)
   result *= i;
 }
}

"So far, so good."

"Great. Then let's examine how a queue object should look. What can you tell me about it?"

"It must be thread-safe. It is loaded with task objects by a thread that receives them from users, and then the tasks are picked up by worker threads."

"Yep. And what if we run out of tasks for a time?"

"Then the worker threads should wait until there are more."

"That's right. Now imagine that all this can be built in a single queue. Check it out:"

A task queue. If there are no tasks, then the thread falls asleep and waits for one to appear:
public class JobQueue
{
 ArrayList jobs = new ArrayList();

 public synchronized void put(Runnable job)
 {
  jobs.add(job);
  this.notifyAll();
 }

 public synchronized Runnable getJob()
 {
  while (jobs.size() == 0)
   this.wait();

  return jobs.remove(0);
 }
}

"We have a getJob method that checks whether the task list is empty. The thread then goes to sleep (waits) until something appears in the list."

"There is also the put method, which lets you add a new task to the list. As soon as a new task is added, the notifyAll method is called. Calling this method awakens all worker threads that fell asleep inside the getJob method."

"Can you remember again how the wait and notify methods work?"

"The wait method is only called inside a synchronized block, on a mutex object. In our case: this. Moreover, two things happen:

1) The thread falls asleep.

2) The thread temporarily releases the mutex (until it wakes up).

"After that, other threads can enter the synchronized block and acquire that same mutex."

"The notifyAll method can also only be called inside the synchronized block of a mutex object. In our case: this. Moreover, two things happen:"

1) All threads waiting on this mutex object are awakened.

2) Once the current thread exits the synchronized block, one of the awakened threads acquires the mutex and continue its work. When it releases the mutex, another awakened thread acquires the mutex, etc.

"It's very similar to a bus. You enter and want to pay your fare, but there's no driver. So you «fall asleep». Eventually, the bus is packed, but there's still no one to give the fare to. Then the driver arrives and says, «Fare, please». And this is the beginning of…"

"Interesting comparison. But what's a bus?"

"Julio explained this. There were these weird things used in the 21st century."