Métodos de objetos de Java: esperar y notificar

M

Introducción

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:

  • void wait() – espera hasta que Object#notify() o Object#noifyAll() se llama
  • void wait(long timeout) – espera a que transcurran los milisegundos especificados o se llama a la notificación
  • void 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 objeto wait fue llamado
  • void 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:

NumberProducer: 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.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad