O

"Olá, amigo!"

"Quero me aprofundar com você sobre notificação de espera. Os métodos de notificação de espera fornecem um mecanismo conveniente para a interação de encadeamentos. Eles também podem ser usados ​​para criar mecanismos complexos de alto nível para interação de encadeamento."

"Vou começar com um pequeno exemplo. Suponha que temos um programa para um servidor que deve executar várias tarefas criadas pelos usuários por meio de um site. Os usuários podem adicionar várias tarefas em momentos diferentes. As tarefas consomem muitos recursos, mas o 8 -core processador pode lidar. Como devemos executar as tarefas no servidor?"

"Primeiro, criaremos um grupo de threads de trabalho, tantos quantos forem os núcleos do processador. Cada thread poderá executar em seu próprio núcleo: os threads não interferirão uns nos outros e os núcleos do processador não sentar ocioso."

"Em segundo lugar, criaremos um objeto de fila onde serão adicionadas as tarefas dos usuários. Diferentes tipos de tarefas corresponderão a diferentes objetos, mas todos eles implementarão a interface Runnable para que possam ser executados."

"Você poderia me dar um exemplo de um objeto de tarefa?"

"Confira:"

Uma classe que calcula n fatorial quando o método run() é chamado
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;
 }
}

"Até agora tudo bem."

"Ótimo. Então vamos examinar como um objeto de fila deve parecer. O que você pode me dizer sobre isso?"

"Deve ser thread-safe. Ele é carregado com objetos de tarefa por um thread que os recebe dos usuários e, em seguida, as tarefas são selecionadas por threads de trabalho."

"Sim. E se ficarmos sem tarefas por um tempo?"

"Então os threads de trabalho devem esperar até que haja mais."

"Isso mesmo. Agora imagine que tudo isso pode ser construído em uma única fila. Confira:"

Uma fila de tarefas. Se não houver tarefas, o thread adormece e aguarda o aparecimento de um:
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);
 }
}

"Temos um método getJob que verifica se a lista de tarefas está vazia. O encadeamento entra em suspensão (espera) até que algo apareça na lista."

"Existe também o método put , que permite adicionar uma nova tarefa à lista. Assim que uma nova tarefa é adicionada, o método notifyAll é chamado. Chamar esse método desperta todos os threads de trabalho que ficaram adormecidos dentro do método getJob."

"Você consegue se lembrar de novo como os métodos de espera e notificação funcionam?"

"O método wait só é chamado dentro de um bloco sincronizado, em um objeto mutex. No nosso caso: this. Além disso, duas coisas acontecem:

1) O fio adormece.

2) O thread libera temporariamente o mutex (até que ele acorde).

"Depois disso, outros threads podem entrar no bloco sincronizado e adquirir o mesmo mutex."

"O método notifyAll também só pode ser chamado dentro do bloco sincronizado de um objeto mutex. No nosso caso: this. Além disso, duas coisas acontecem:"

1) Todos os encadeamentos que aguardam neste objeto mutex são ativados.

2) Uma vez que o thread atual sai do bloco sincronizado, um dos threads acordados adquire o mutex e continua seu trabalho. Quando ele libera o mutex, outro thread ativado adquire o mutex, etc.

"É muito parecido com um ônibus. Você entra e quer pagar a passagem, mas não tem motorista. Então você 'cai no sono'. Eventualmente, o ônibus está lotado, mas ainda não há ninguém para pagar a passagem. Então o motorista chega e diz: «Passe, por favor». E este é o começo de…"

"Comparação interessante. Mas o que é um ônibus?"

"Julio explicou isso. Havia essas coisas estranhas usadas no século 21."