Introducción
Contenido
Este artículo es el tutorial final de una serie que describe los métodos a menudo olvidados de la clase Object base del lenguaje Java. Los siguientes son los métodos del objeto Java base que están presentes en todos los objetos Java debido a la herencia implícita de Object.
- Encadenar
- a clase
- es igual a
- código hash
- clon
- finalizar
- espera y notifica (estás aquí)
El enfoque de este artículo son los Object#wait()
y Object#notify
métodos (y sus variaciones) que se utilizan para comunicar y coordinar el control entre subprocesos de una aplicación multiproceso.
Descripción básica
los Object#wait()
El método se usa dentro de un bloque de sincronización o método miembro y hace que el hilo en el que se llama espere indefinidamente hasta que otro hilo llame Object#notify()
(o es variación Object#notifyAll()
) en el mismo objeto que el original Object#wait()
fue llamado.
Wait tiene tres variaciones:
Te puede interesar:Tutorial de Dropwizard: Desarrolle servicios web RESTful más rápidovoid wait()
– espera hasta queObject#notify()
oObject#noifyAll()
se llamavoid wait(long timeout)
– espera a que transcurran los milisegundos especificados o se llama a la notificaciónvoid wait(long timeout, int nanos)
– igual que el anterior pero, con la precisión extra de los nanosegundos suministrados
los Object#notify()
se utiliza para despertar un solo hilo que está esperando en un objeto que wait
fue llamado. Tenga en cuenta que en el caso de varios subprocesos esperando en el objeto, el subproceso despertado es seleccionado aleatoriamente por el sistema operativo
Notificar tiene tres variaciones:
void notify()
– selecciona aleatoriamente y despierta un hilo que espera en el objetowait
fue llamadovoid notifyAll()
– despierta todos los hilos que esperan en el objeto
El problema clásico del consumidor productor
Como todas las cosas en programación, estos conceptos de uso Object#wait()
y Object#notify()
se entienden mejor a través de un ejemplo cuidadosamente pensado. En este ejemplo, voy a implementar una aplicación de productor / consumidor de subprocesos múltiples para demostrar el uso de wait
y notify
. Esta aplicación utilizará un productor para generar un número entero aleatorio que representará un número de números aleatorios pares que los hilos del consumidor necesitarán generar aleatoriamente.
El diseño y las especificaciones de la clase para este ejemplo son los siguientes:
Te puede interesar:Cómo convertir una cadena en fecha en JavaNumberProducer
: produce un número entero aleatorio entre 1-100 que representa el número de números pares aleatorios que un consumidor necesitará generar. El productor debe colocar el número aleatorio en una cola donde un consumidor puede recuperarlo y comenzar a trabajar produciendo números pares aleatorios.
NumberQueue
: una cola que pondrá en cola un número del productor y quitará ese número a un consumidor que espera ansiosamente la oportunidad de generar una serie de números pares aleatorios
NumberConsumer
: un consumidor que recuperará un número de la cola que representa el número de enteros pares aleatorios para generar
los NumberQueue
.
import java.util.LinkedList;
public class NumberQueue {
private LinkedList<Integer> numQueue = new LinkedList<>();
public synchronized void pushNumber(int num) {
numQueue.addLast(num);
notifyAll();
}
public synchronized int pullNumber() {
while(numQueue.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return numQueue.removeFirst();
}
public synchronized int size() {
return numQueue.size();
}
}
NumberQueue
tiene un LinkedList
que contendrá los datos numéricos internamente y proporcionará acceso a ellos a través de tres métodos sincronizados. Aquí los métodos están sincronizados para que se coloque un candado en el acceso al LinkedList
estructura de datos que garantiza que, como máximo, solo un hilo puede tener control sobre el método a la vez. Además, el NumberQueue#pushNumber
llamadas al método es heredado Object#notifyAll
método al agregar un nuevo número para informar a los consumidores que hay trabajo disponible. Del mismo modo, el NumberQueue#pullNumber
El método usa un bucle junto con una llamada a su herencia. Object#wait
método para suspender la ejecución si no tiene números en su lista hasta que tenga datos para los consumidores.
los NumberProducer
clase.
import java.util.Random;
public class NumberProducer extends Thread {
private int maxNumsInQueue;
private NumberQueue numsQueue;
public NumberProducer(int maxNumsInQueue, NumberQueue numsQueue) {
this.maxNumsInQueue = maxNumsInQueue;
this.numsQueue = numsQueue;
}
@Override
public void run() {
System.out.println(getName() + " starting to produce ...");
Random rand = new Random();
// continuously produce numbers for queue
while(true) {
if (numsQueue.size() < maxNumsInQueue) {
// random numbers 1-100
int evenNums = rand.nextInt(99) + 1;
numsQueue.pushNumber(evenNums);
System.out.println(getName() + " adding " + evenNums);
}
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
NumberProducer
hereda el Thread
class y contiene un campo llamado maxNumsInQueue
que pone un límite en el número de elementos que la cola puede contener, y también tiene una referencia a la NumberQueue
instancia a través de su numsQueue
field, que obtiene a través de un solo constructor. Anula el Thread#run
método que contiene un bucle infinito que agrega un número entero aleatorio entre 1-100 al NumberQueue
cada 800 milisegundos. Esto sucede siempre que la cola esté dentro de su límite, llenando así la cola y gobernando el trabajo para los consumidores.
los NumberConsumer
clase.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.StringJoiner;
public class NumberConsumer extends Thread {
private NumberQueue numQueue;
public NumberConsumer(NumberQueue numQueue) {
this.numQueue = numQueue;
}
@Override
public void run() {
System.out.println(getName() + " starting to consume ...");
Random rand = new Random();
// consume forever
while(true) {
int num = numQueue.pullNumber();
List<Integer> evens = new ArrayList();
while(evens.size() < num) {
int randInt = rand.nextInt(999) + 1;
if (randInt % 2 == 0) {
evens.add(randInt);
}
}
String s = " " + getName() + " found " + num + " evens [";
StringJoiner nums = new StringJoiner(",");
for (int randInt : evens) {
nums.add(Integer.toString(randInt));
}
s += nums.toString() + "]";
System.out.println(s);
}
}
}
NumberConsumer
también hereda de Thread
y mantiene una referencia a la NumberQueue
mediante el numQueue
campo de referencia obtenido a través de su constructor. Su método de ejecución anulado también contiene un bucle infinito, que en su interior extrae un número de la cola a medida que están disponibles. Una vez que recibe el número, ingresa a otro ciclo que produce números enteros aleatorios del 1 al 1000, prueba su uniformidad y los agrega a una lista para su posterior visualización.
Una vez que encuentra el número requerido de números pares aleatorios especificados por el num
variable sacada de la cola sale del bucle interno y proclama a la consola sus hallazgos.
los EvenNumberQueueRunner
clase.
public class EvenNumberQueueRunner {
public static void main(String[] args) {
final int MAX_QUEUE_SIZE = 5;
NumberQueue queue = new NumberQueue();
System.out.println(" NumberProducer thread NumberConsumer threads");
System.out.println("============================= =============================");
NumberProducer producer = new NumberProducer(MAX_QUEUE_SIZE, queue);
producer.start();
// give producer a head start
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
NumberConsumer consumer1 = new NumberConsumer(queue);
consumer1.start();
NumberConsumer consumer2 = new NumberConsumer(queue);
consumer2.start();
}
}
EvenNumberQueueRunner
es la clase principal en esta aplicación que comienza instanciando el NumberProducer
class y lo lanza como un hilo. Luego le da una ventaja de 3 segundos para llenar su cola con el número máximo de números pares que se generarán. Finalmente, el NumberConsumer
class se instancia dos veces y los lanza como subprocesos que luego salen sacando números de la cola y creando el número indicado de enteros pares.
Aquí se muestra un ejemplo de salida del programa. Tenga en cuenta que no es probable que dos ejecuciones produzcan el mismo resultado, ya que esta aplicación es de naturaleza puramente aleatoria, desde los números producidos hasta la aleatoriedad de que el sistema operativo cambia entre subprocesos activos en la CPU.
NumberProducer thread NumberConsumer threads
============================= =============================
Thread-0 starting to produce ...
Thread-0 adding 8
Thread-0 adding 52
Thread-0 adding 79
Thread-0 adding 62
Thread-1 starting to consume ...
Thread-2 starting to consume ...
Thread-1 found 8 evens [890,764,366,20,656,614,86,884]
Thread-2 found 52 evens [462,858,266,190,764,686,36,730,628,916,444,370,860,732,188,652,274,608,912,940,708,542,760,194,642,192,22,36,622,174,66,168,264,472,228,972,18,486,714,244,214,836,206,342,388,832,8,666,946,116,342,62]
Thread-2 found 62 evens [404,378,276,308,470,156,96,174,160,704,44,12,934,426,616,318,942,320,798,696,494,484,856,496,886,828,386,80,350,920,142,686,118,240,398,488,976,512,642,108,542,122,536,482,734,430,564,200,844,462,12,124,368,764,496,728,802,836,478,986,292,486]
Thread-1 found 79 evens [910,722,352,656,250,974,602,342,144,952,916,188,286,468,618,496,764,642,506,168,966,274,476,744,142,348,784,164,346,344,48,862,754,896,896,784,574,464,134,192,446,524,424,710,128,756,934,672,816,604,186,18,432,250,466,144,930,914,670,434,764,176,388,534,448,476,598,984,536,920,282,478,754,750,994,60,466,382,208]
Thread-0 adding 73
Thread-2 found 73 evens [798,692,698,280,688,174,528,632,528,278,80,746,790,456,352,280,574,686,392,26,994,144,166,806,750,354,586,140,204,144,664,214,808,214,218,414,230,364,986,736,844,834,826,564,260,684,348,76,390,294,740,550,310,364,460,816,650,358,206,892,264,890,830,206,976,362,564,26,894,764,726,782,122]
Thread-0 adding 29
Thread-1 found 29 evens [274,600,518,222,762,494,754,194,128,354,900,226,120,904,206,838,258,468,114,622,534,122,178,24,332,432,966,712,104]
Thread-0 adding 65
... and on and on ...
Me gustaría tomarme un momento para explicar mi uso de la notifyAll()
método dentro NumberQueue#pushNumber
porque mi elección no fue aleatoria. Usando el notifyAll()
Método Le estoy dando a los dos hilos consumidores la misma oportunidad de sacar un número de la cola para trabajar en lugar de dejar que el sistema operativo elija uno sobre el otro. Esto es importante porque si simplemente hubiera usado notify()
entonces existe una buena posibilidad de que el hilo que el sistema operativo selecciona para acceder a la cola aún no esté listo para hacer más trabajo y esté trabajando en su último conjunto de números pares (bueno, es un poco descabellado que todavía esté tratando de encontrar hasta un máximo de 1000 números pares después de 800 milisegundos, pero espero que entiendas a qué me refiero). Básicamente, lo que quiero dejar claro aquí es que en casi todos los casos debe preferir el notifyAll()
método sobre el notify()
variante.
Conclusión
En este artículo final de la serie de métodos de clases de objetos Java, he cubierto el propósito y el uso de las variaciones de wait
y notify
. Cabe decir que estos métodos son bastante primitivos y los mecanismos de concurrencia de Java han evolucionado desde entonces pero, en mi opinión wait
y notify
todavía son un valioso conjunto de herramientas para tener en su cinturón de herramientas de programación Java.
Como siempre, gracias por leer y no dude en comentar o criticar a continuación.
Te puede interesar:Cómo desarrollar un proyecto Maven en Eclipse