java - method - ¿Notifica/notifica la liberación de la cerradura que se retiene
lock object java (8)
Digamos que el grupo de lectores quiere leer el valor actualizado de ciertos recursos, que será actualizado por Writer. Entonces, cómo Reader sabe que Writer ha actualizado los Campos de recursos.
Por lo tanto, para sincronizar tales casos entre lectores y escritores en un recurso común, se han utilizado tres métodos finales de clase de objeto.
- Espere()
- notificar()
- notificar a todos ()
Espera : los lectores quieren leer el valor actualizado del recurso, se registran con el objeto del recurso, es decir, cuando la actualización ocurre en el mismo objeto y cuando el Escritor lo notifica, los lectores intentarán adquirir un bloqueo en el recurso y leer el recurso actualizado. - Espere solo ser invocado cuando Reader tenga Lock Object, aquí en nuestro caso es un recurso. - Una vez que se llama al método de espera, Reader libera el objeto de bloqueo. - Ahora solo para el mismo objeto registrado (recurso) Reader obtendrá señales de notificación. - Si Reader invoca la espera en el Objeto, que es diferente del Object Writer utilizado para enviar notificaciones, Reader nunca recibirá la señal de notificación. - Una vez que se notifica a los Lectores, ahora los Lectores intentarán contentarse con el bloqueo (uno de ellos obtiene el bloqueo) leer el valor actualizado del recurso. Del mismo modo, otros lectores también obtienen turnos para adquirir bloqueo y leer el valor actualizado. - Una vez que el Lector haya leído el valor actualizado, ejecute la lógica de negocios y salga del Bloque sincronizado, el Lector liberará el bloqueo para que otros Lectores puedan adquirirlo.
Notificar : el Escritor ingresa al Bloque sincronizado, luego de que el bloqueo realice su lógica de negocios, actualice el recurso Objeto, una vez que se actualice el recurso, notificará a los hilos en espera (Lectores) que están esperando en el mismo Bloqueo. - Notifique la señal a un solo subproceso en espera, que se decide mediante el administrador de subprocesos de Java subyacente - Una vez que el escritor señala notifica (), entonces no significa que Reader se apresuró a leer los valores de las actualizaciones. Primero, el escritor debe liberar el bloqueo, lo que hará una vez que salga del bloque sincronizado. Una vez que se libere el bloqueo y se notifiquen los hilos en espera, entonces [En caso de notificar ()] notificada, el subproceso adquirirá el bloqueo [Lanzado por el autor] y luego ingrese el Bloque sincronizado y finalice desde donde se fue [es decir, declaraciones después de esperar ()].
Notificar a todos : En notificationAll, todos los subprocesos registrados con bloqueo de recursos recibirán las notificaciones. - Una vez que se activa NotifyAll (), todos los subprocesos que esperan en el mismo bloqueo obtendrán la señal y estarán listos en contienda para adquirir el bloqueo. - Una vez que Writer finaliza su trabajo y libera el bloqueo, cualquier lector adquirirá el bloqueo [que Thread, nuevamente decidido por la implementación subyacente de Java Thread Manager]. - Una vez que el Reader obtiene el bloqueo, ingresará en el Bloque sincronizado, donde dejó el método [es decir, después de esperar ()], realiza las tareas y al completar el Bloque sincronizado libera el bloqueo. - Ahora, los otros subprocesos restantes intentarán adquirir el bloqueo, cualquiera de ellos lo obtendrá, entrará en el bloque sincronizado, completará su tarea y luego liberará el bloqueo. - Este proceso continuará hasta que todos los lectores registrados completen el trabajo.
Ahora veremos el Código para ello. También discutiremos el Código también. :
Descripción general básica del código: consta de tres clases
- Clase de recurso: en el que se adquirirá el bloqueo y se esperará () y notificar (), se invocará a NotifyAll ().
- ReaderTask: implementa la interfaz ejecutable, implica trabajos de lectores, desea leer el valor actualizado del objeto de recurso.
- WriterTask: implementa la interfaz ejecutable, implica trabajos de escritura, actualizará el objeto de recurso y notificará los subprocesos en espera registrados.
- Demo Class: que creará Let say 3 Readers y 1 Writer Thread, enlaza tareas respectivas e inicia los threads.
Resource.java
public class Resource {
private String mesg;
public void setMesg(String mesg){
this.mesg =mesg;
}
public String getMesg(){
return this.mesg;
}
}
WaitThreadTask.java
public class WaitThreadTask implements Runnable {
private Resource resource;
public WaitThreadTask(Resource resource){
this.resource = resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(resource){
System.out.println("Before Reading Updated Value By : " +Thread.currentThread().getName() );
//We need to Take care to get the updated value, so waiting for writer thread to update value.
try {
//Release resource Lock & wait till any notification from Writer.
resource.wait();
System.out.println("Waiting is Over For : "+ Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Read Updated Value
System.out.println("Updated Value of Resource Mesg :" + resource.getMesg() + " Read By :" +Thread.currentThread().getName());
}
}
}
WriterThreadTask.java
public class WriterThreadTask implements Runnable{
private Resource resource;
public WriterThreadTask(Resource resource){
this.resource = resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(resource){
System.out.println("Before Updating Resource By : " + Thread.currentThread().getName());
//Updating resource Object Message
resource.setMesg("Hi How are You !!!");
resource.notify();
//resource.notifyAll();
//Once Writer Comes Out from Synch Block, Readers will Content to read the values.
System.out.println("Task Done By Writer Thread.");
}
}
}
ThreadDemo.java
public class ThreadDemo {
public static void main(String args[]){
//Create Single Resource Object, which can act as Lock on Writer and Readers.
Resource lock = new Resource();
//Three Readers and One Writer runnable Tasks.
Runnable taskR1 = new WaitThreadTask(lock);
Runnable taskR2 = new WaitThreadTask(lock);
Runnable taskR3 = new WaitThreadTask(lock);
Runnable taskW1 = new WriterThreadTask(lock);
Thread t1 = new Thread(taskR1, "Reader1");
Thread t2 = new Thread(taskR2, "Reader2");
Thread t3 = new Thread(taskR3, "Reader3");
Thread t4 = new Thread(taskW1, "Writer1");
t1.start();
t2.start();
t3.start();
/*try{
Thread.sleep(5000);
} catch(InterruptedException e){
e.printStackTrace();
}*/
t4.start();
}
}
Código de observaciones :
- Tanto Notify () / notifyAll () y wait (): funciona solo en los objetos de bloqueo que ya adquieren. Por ejemplo: Synchornized (ObjectA) {...... // ... // ObjectB.wait () o ObjectB.notify () o ObjectB.notifyAll () ...} luego lanzará la excepción IllegalMonitorStateException. Por lo tanto, se debe tener cuidado con el bloqueo que debe adquirirse antes de llamar a cualquiera de los tres métodos anteriores con el mismo bloqueo.Incluso si simplemente escribe el comando notificar () o espere () o notifique a Todo (), entonces se lanzará la excepción IllegalMonitorStateException porque [Se sugiere el bloqueo ser adquirido en este objeto, una vez más, que no es el caso].
- El lector solo podrá recibir señales en las que se envíe la misma notificación. Si ocurre una espera en el Objeto que es diferente del Objeto en el que se está enviando la notificación, los Lectores nunca recibirán la notificación y, por lo tanto, esperarán por siempre.
- Lectores que están registrados antes de que Writer pueda enviar la notificación, solo esos lectores la recibirán. Porque si Writer envía primero una notificación, antes de que el lector se registre en Object, no recibirán las señales ya que las señales ya se están perdiendo: Señales perdidas
- Reader y Writer deben adquirir Bloqueo en el mismo objeto e invocar señales de espera / notificación en el mismo objeto . Si el código anterior se modifica como, en lugar de usar el recurso para bloqueos, espere y notifique, si usamos esto. Lo que sucederá ? Bien ... Todos los lectores esperarán para siempre, porque los lectores registrados con diferentes Objetos de WaitThreadTask y el escritor notifican en WriterThreadTask. Por lo tanto, ninguno de los lectores recibirá señales de notificación, ya que se registraron para recibir señales en el objeto WaitThreadTask respectivo y no en el objeto WriterThreadTask.
Estoy confundido un poco acerca de esperar y notificar / notificar a todos.
Sé que hay un bloqueo para cada objeto java. Sé que esperar liberará el bloqueo para otro hilo. ¿Qué hay de notificar / notificar? ¿Avisa / notifica que Todos liberan el bloqueo que mantiene para otro hilo?
El método de notify()
llamada en un objeto libera el bloqueo de ese objeto. Pero esto no es como llamar al método wait()
.
Así que aquí está cómo es:
Si un subproceso llama al método wait()
en un objeto, el subproceso libera INMEDIATAMENTE el bloqueo de ese objeto y pasa al estado de espera.
Pero cuando un subproceso llama al método de notify()
en un objeto, el subproceso no puede liberar el bloqueo de ese objeto inmediatamente, ya que el subproceso puede tener más trabajo por hacer. Al final, el subproceso liberará el bloqueo del objeto cuando se llame al método de notify()
, ya que el subproceso en espera necesitará el bloqueo del objeto para continuar la ejecución después de que se haya notificado.
Para aclarar mi comprensión y proporcionar un ejemplo para que todos se muestren cuando se libera el bloqueo, agregué declaraciones de impresión al siguiente código después de la llamada para notificar () / NotifyAll ():
class ThreadDemo {
public static void main(String[] args) {
Shared s = new Shared();
new Producer(s).start();
new Consumer(s).start();
}
}
class Shared {
private char c = ''/u0000'';
private boolean writeable = true;
synchronized void setSharedChar(char c) {
while (!writeable)
try {
wait();
} catch (InterruptedException e) {
}
this.c = c;
writeable = false;
notifyAll();
System.out.println("setSharedChar notify() called - still in synchronized block.");
}
synchronized char getSharedChar() {
while (writeable)
try {
wait();
} catch (InterruptedException e) {
}
writeable = true;
notifyAll();
System.out.println("getSharedChar notify() called - still in synchronized block.");
return c;
}
}
class Producer extends Thread {
private Shared s;
Producer(Shared s) {
this.s = s;
}
public void run() {
System.out.println("Starting producer thread.");
for (char ch = ''A''; ch <= ''Z''; ch++) {
System.out.println("Producer thread getting ready to create a char.");
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
s.setSharedChar(ch);
System.out.println(ch + " produced by producer.");
}
}
}
class Consumer extends Thread {
private Shared s;
Consumer(Shared s) {
this.s = s;
}
public void run() {
System.out.println("Starting consumer thread.");
char ch;
do {
System.out.println("Consumer thread getting ready to read a char.");
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
ch = s.getSharedChar();
System.out.println(ch + " consumed by consumer.");
} while (ch != ''Z'');
}
}
Cuando ejecuté este ejemplo suficientes veces, hubo un punto en el que finalmente vi el resultado del programa:
...
F produced by producer.
Producer thread getting ready to create a char.
getSharedChar notify() called - still in synchronized block.
F consumed by consumer.
Consumer thread getting ready to read a char.
setSharedChar notify() called - still in synchronized block.
G produced by producer.
Producer thread getting ready to create a char.
getSharedChar notify() called - still in synchronized block.
setSharedChar notify() called - still in synchronized block.
G consumed by consumer.
Dado que la salida getSharedChar puede aparecer antes de setSharedChar, parece que la cerradura se está liberando de inmediato o no es necesario volver a ingresar a la función getSharedChar () sincronizada mediante la llamada a notificationAll (). Es posible que el bloqueo aún esté en su lugar, pero si puede volver a ingresar a la función sin él, ¿cuál es la diferencia? Pude ver una salida similar que sustituye a Notify () para notifyAll (). Esto se hizo en Java 1.7.0_15 en un sistema Windows 7 de 64 bits.
Tengo que estar en desacuerdo con las personas que dicen que notifyAll()
libera el bloqueo en el objeto sobre el cual se están sincronizando los hilos en espera y de notificación.
Un ejemplo:
Consumer
clase del Consumer
contiene un bloque:
synchronized(sharedObject){
if(sharedObject.isReadyToConsume() == false){
sharedObject.wait();
}else {
sharedObject.doTheThing();
System.out.println("consumer consuming...");
}
}
Escenario: la clase del consumidor obtiene el bloqueo en el objeto sharedObject , ingresa exclusivamente (está dentro del bloque de sincronización) y ve que sharedObject todavía no tiene nada listo (nada que consumir :)) y llama al método wait()
en el sharedObject . De esa manera, libera el bloqueo (¡detiene la ejecución allí!) Y espera a que se le notifique que continúe cuando otro Thread (tal vez el Productor) llame a sharedObject.notify();
o sharedObject.notifyAll();
. Cuando se le notifica, continúa desde la línea de espera ().
Es el sharedObject que realiza un seguimiento de los subprocesos que pidieron que se le notifique. Cuando algunos subprocesos llaman al método sharedObject.notifyAll () , el sharedObject notificará a los subprocesos en espera para que se activen ... Ahora, la parte difícil es que un subproceso libera naturalmente el bloqueo del objeto cuando llega al final de su sincronización (sharedObject) bloque {} La pregunta es ¿qué sucede si llamo a notificarAll () en ese bloque ? notifyAll () despierta los hilos en espera, pero el bloqueo aún es propiedad del hilo que acaba de llamar a notificarAll ()
Mira el fragmento de productor:
synchronized(sharedObject){
//We are exlusively working with sharedObject and noone can enter it
[... changing the object ...]
sharedObject.notifyAll(); //notifying the waiting threads to wake up
Thread.sleep(1000); //Telling the current thread to go to sleep. It''s holding the LOCK
System.out.println("awake...");
}
Si NotifyAll () liberaría el bloqueo, el mensaje "awake ..." se imprimirá después de que las clases de Consumer empiecen a trabajar con sharedObject . Este no es el caso ... La salida muestra que el consumidor está consumiendo el sharedObject después de que el productor salga de su bloque de sincronización ...
- wait () : libera el bloqueo y continúa en la siguiente línea cuando se le notifica
- notificar (), notificarAll () - no liberar el bloqueo. Simplemente hacen que los hilos de espera sean ejecutables nuevamente (no inactivos). Tendrán derecho a ingresar cuando el subproceso actual llegue al final de su bloque de sincronización y el programador de subprocesos les diga que el bloqueo se ha liberado. La lucha por la cerradura comienza de nuevo.
wait (): Prácticamente todos los objetos en Java poseen un monitor, para ingresar dentro de cualquier bloque sincronizado que un subproceso debe adquirir primero, y luego solo él puede ingresar a este bloque sincronizado. Como la sección crítica del código es ejecutada por un solo hilo a la vez, tiene un gran impacto en el rendimiento general de la aplicación. Por lo tanto, en lugar de mantener los recursos (monitor), se le puede pedir que abandone la sección crítica y espere algún tiempo. Para lograr este comportamiento, Java ha proporcionado una api wait () directamente en la clase Object.
Por lo tanto, cada vez que un subproceso encuentra una API de espera (), descarta el monitor actual y todos los demás monitores que tiene y pasa al estado de espera vinculado al objeto actual. Es importante entender que eso pasó al estado de espera en el contexto del objeto para el cual el hilo adquirió el monitor primero. De manera conceptual, explico, cada objeto contiene una casa contenedor donde se guardan todos los hilos en espera. Hay varias formas en que un Hilo puede salir de esta casa contenedor de Objeto. Veamos..
- Cuando aparece otro hilo y suena el timbre una vez, en Java, se llama al método de notificación () en el mismo objeto.
- Cuando aparece otro hilo y suena varias veces, uno de los hilos tiene la oportunidad de salir del contenedor de objetos de Object. En Java podemos hacerlo llamando a notifyAll () en el mismo objeto.
- Si tenemos la referencia del hilo que espera en contenedor de casa. Llamar a interrupt () en el objeto Thread lo saca del estado de espera y le trae el bloque de excepción del objeto.
- Hay métodos sobrecargados de espera (milisegundos largos) y de espera (largos millSec, int nanos). A medida que el tiempo sobre el subproceso es elegible para salir del estado de espera y competir nuevamente para el monitor de objetos. En el caso de que Thread no pueda adquirir el monitor después de un tiempo de espera, también tendrá que esperar solo las llamadas de notificación ().
notificar (): si la casa del contenedor de objetos tiene varios subprocesos en estado de espera, al llamar a notificar () en este objeto se le da la oportunidad de continuar el subproceso. Pero después de salir del estado de espera, el subproceso aún debe competir por el monitor de objetos y, si logra que el monitor continúe, su ejecución, de lo contrario, el subproceso volverá al estado de espera. Así que notificar () también tiene que ser llamado desde el bloque sincronizado. Si no se llama a la notificación () desde el contexto sincronizado, entonces se obtiene la excepción IllegalMonitorStateException.
notifyAll (): Al llamar a NotifyAll () en el Objeto, se garantiza que todos los subprocesos de la casa del contenedor de Objetos se activen, pero una vez que están activados deben competir entre sí o cualquier otro subproceso desea adquirir el monitor de objetos. Cualquiera que sea el hilo que tenga éxito, continúe con sus ejecuciones, otros tienen que volver al estado de espera y establecerse en el contenedor de objetos. Como Notify (), NotifyAll () también debe llamarse en el contexto sincronizado.
Explicación tomada de http://coder2design.com/thread-communication/
No: notify
/ notify
. No libere bloqueos como wait
. El subproceso despertado no puede ejecutarse hasta que el código llamado notify
libere su bloqueo.
Esto es lo que dice el Javadoc:
El subproceso libera la propiedad de este monitor y espera hasta que otro subproceso notifique a los subprocesos que esperan en el monitor de este objeto para que se activen a través de una llamada al método de notificación o el método de notificación de todos. El hilo luego espera hasta que pueda volver a obtener la propiedad del monitor y reanudar la ejecución.
public class ProducerConsumerInJava {
public static void main(String args[]) {
System.out.println("How to use wait and notify method in Java");
System.out.println("Solving Producer Consumper Problem");
Queue<Integer> buffer = new LinkedList<>();
int maxSize = 10;
Thread producer = new Producer(buffer, maxSize, "PRODUCER");
Thread consumer = new Consumer(buffer, maxSize, "CONSUMER");
producer.start();
consumer.start();
}
}
class Producer extends Thread {
private Queue<Integer> queue;
private int maxSize;
public Producer(Queue<Integer> queue, int maxSize, String name){
super(name); this.queue = queue; this.maxSize = maxSize;
}
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == maxSize) {
try {
System.out .println("Queue is full, " +
"Producer thread waiting for " + "consumer to take
something from queue");
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
Random random = new Random();
int i = random.nextInt();
System.out.println("Producing value : " + i);
queue.add(i);
queue.notifyAll();
}
}
}
}
class Consumer extends Thread {
private Queue<Integer> queue;
private int maxSize;
public Consumer(Queue<Integer> queue, int maxSize, String name){
super(name); this.queue = queue; this.maxSize = maxSize;
}
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out .println("Queue is empty," +
"Consumer thread is waiting" +
" for producer thread to put something in queue");
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
System.out.println("Consuming value : " + queue.remove());
queue.notifyAll();
}
}
}
}
Este es un ejemplo del programa Consumidor y Productor.
La salida del programa anterior después de la ejecución se escribe a continuación:
How to use wait and notify
method in Java Solving Producer Consumper Problem
Queue is empty,Consumer thread is waiting for producer thread to put
something in queue
Producing value : -1692411980
Producing value : 285310787
Producing value : -1045894970
Producing value : 2140997307
Producing value : 1379699468
Producing value : 912077154
Producing value : -1635438928
Producing value : -500696499
Producing value : -1985700664
Producing value : 961945684
Queue is full, Producer thread waiting for consumer to take something from
queue Consuming value : -1692411980
Consuming value : 285310787
Consuming value : -1045894970
Consuming value : 2140997307
Consuming value : 1379699468
Consuming value : 912077154
Consuming value : -1635438928
Consuming value : -500696499
Consuming value : -1985700664
Consuming value : 961945684
Queue is empty,Consumer thread is waiting for producer thread to put
something in queue
Producing value : 118213849
Entonces, lo que podemos concluir es que notificar a Todos () o notificar () no liberará el bloqueo. Eche un vistazo a la salida, el valor de producción y el valor de consumo no se imprimen alternativamente, es decir, se imprimen por separado.
Por lo tanto, notificar / notificar que no liberará el bloqueo
Lea más: http://javarevisited.blogspot.com/2015/07/how-to-use-wait-notify-and-notifyall-in.html#ixzz57kdToLX6
wait () le dice al subproceso que llama que renuncie al monitor y se ponga en suspensión hasta que otro subproceso ingrese en el mismo monitor y las llamadas notifiquen ().
notificar () despierta un hilo que llama a espera () en el mismo objeto.
notifyAll () activa todos los subprocesos que llamaron a wait () en el mismo objeto. El hilo de mayor prioridad se ejecutará primero.