Sincronización de hilos Java <Br /> Cuando dos o más hilos necesitan compartir recursos, necesitan alguna forma de determinar que el recurso está ocupado por un solo hilo en un momento determinado. El proceso de lograr este objetivo se llama sincronización. Como puede ver, Java proporciona soporte único a nivel de idioma para esto.
La clave para la sincronización es el concepto de tubería (también llamado semáforo). Un mutex es un objeto bloqueado mutex o un mutex. En un momento dado, solo un hilo puede obtener el proceso de gestión. Cuando un hilo necesita ser bloqueado, debe ingresar a la tubería. Todas las demás roscas que intentan ingresar a la tubería bloqueada deben suspenderse hasta que la primera rosca salga de la tubería. Estos otros hilos se llaman procesos de gestión de espera. Un hilo con un proceso de gestión puede ingresar nuevamente el mismo proceso de gestión si lo desea.
Si ha usado la sincronización en otros idiomas, como C o C ++, sabrá que es un poco extraño de usar. Esto se debe a que muchos idiomas no respaldan la sincronización ellos mismos. En cambio, para los hilos sincrónicos, los programas deben utilizar el lenguaje de origen del sistema operativo. Afortunadamente, Java implementa la sincronización a través de elementos del lenguaje, y la mayoría de las complejidades relacionadas con la sincronización se eliminan.
Puede sincronizar el código de dos maneras. Ambos incluyen el uso de palabras clave sincronizadas, y los siguientes son los dos métodos.
Usando el método de sincronización
La sincronización en Java es simple porque todos los objetos tienen sus procedimientos implícitos correspondientes. Ingresar el proceso de gestión de cierto objeto es llamar al método modificado por la palabra clave sincronizada. Cuando un hilo está dentro de un método de sincronización, todos los demás hilos en la misma instancia que intentan llamar al método (u otro método de sincronización) deben esperar. Para salir del proceso de gestión y renunciar al control sobre el objeto a otros subprocesos que esperan, los hilos con procesos de administración solo necesitan regresar del método de sincronización.
Para comprender la necesidad de sincronización, comencemos con un ejemplo simple en el que se debe usar la sincronización, pero no es útil. El siguiente programa tiene tres clases simples. Primero es CallMe, que tiene un método simple para llamar (). El método Call () tiene un parámetro de cadena llamado Msg. Este método intenta imprimir una cadena MSG en los soportes cuadrados. Lo interesante es que después de llamar a llamar () imprime el soporte izquierdo y la cadena MSG, se llama a Thread.sleep (1000), que detiene el hilo actual por 1 segundo.
El constructor de la siguiente clase que llama se refiere a una instancia de CallMe y una cadena, que están presentes en Target y MSG respectivamente. El constructor también crea un nuevo hilo que llama al método run () del objeto. El hilo comienza de inmediato. El método run () de la clase de llamadas llama al método de llamada () del destino de instancia de llamada a través de la cadena MSG. Finalmente, la clase Synch comienza con la creación de una instancia simple de CallMe y tres instancias de llamadas con diferentes cadenas de mensajes.
La misma instancia de CallMe se pasa a cada instancia de llamadas.
// Este programa no está sincronizado. CallMe de clases {Call void (String Msg) {System.out.print ("[" + MSG); out .println ("Interrupted"); Targ; Cal lME Target = new CallMe (); Espere que los hilos finalen {ob1.t.Join (); }}
La salida de este programa es la siguiente:
Hola [sincronizado [mundo]]]
En este ejemplo, llamando a Sleep (), el método Call () permite que la ejecución se realice a otro hilo. Este resultado es una salida mixta de tres cadenas de mensajes. En este programa, no hay ningún método que evite que tres hilos llamen al mismo método del mismo objeto al mismo tiempo. Esta es una competencia porque tres hilos compiten para completar el método. El ejemplo usa Sleep () para que el efecto se repita y obvio. En la mayoría de los casos, la competencia es más compleja e impredecible porque no puede estar seguro de cuándo ocurrirá la transformación del contexto. Esto hace que el programa se ejecute normalmente y, a veces, error.
Para lograr el propósito deseado en el ejemplo anterior, debe tener el derecho de usar Call (). Es decir, en algún momento, debe limitarse a un solo hilo que puede controlarlo. Para hacer esto, solo necesita preparar la definición de llamada () sincronizada, como sigue:
clase CallMe {Call sincronizado Void (String Msg) {...
Esto evita que otros hilos ingresen a la llamada () cuando un hilo usa la llamada (). Después de que se agrega sincronizado a la llamada (), el programa sale de la siguiente manera:
[Hola] [Sincronizado] [Mundo]
En cualquier momento en situaciones de lectura múltiple, si tiene un método o múltiples métodos para manipular el estado interno del objeto, debe usar la palabra clave sincronizada para evitar la competencia de estado. Recuerde que una vez que un hilo ingresa al método de sincronización de la instancia, ningún otro hilo puede ingresar el método de sincronización de la misma instancia. Sin embargo, aún se pueden llamar a otros métodos de asíncrono de esta instancia.
Declaraciones sincrónicas
Aunque crear un método de sincronización dentro de una clase creada es una forma simple y efectiva de obtener sincronización, no es efectivo en todo momento. Piense en las razones detrás de esto. Supongamos que desea obtener acceso sincronizado a objetos de clase que no están diseñados para el acceso multiproceso, es decir, la clase no usa el método sincronizado. Además, la clase no es creada por usted, sino por un tercero, y no puede obtener su código fuente. De esta manera, no puede preparar el modificador sincronizado en el método relevante. ¿Cómo se puede sincronizar un objeto de esta clase? Afortunadamente, la solución es simple: solo necesita poner la llamada al método definido por esta clase en un bloque sincronizado.
Aquí está la forma normal de declaración sincronizada:
Sincronizado (objeto) {// declaraciones a sincronizar}
Donde, el objeto es una referencia al objeto sincronizado. Si todo lo que desea sincronizar es solo una declaración, entonces no hay necesidad de aparatos ortopédicos. Un bloque de sincronización asegura que la llamada al método de miembro del objeto solo ocurra después de que el hilo actual ingrese con éxito la tubería de objeto.
La siguiente es una versión modificada del programa anterior, y el bloque de sincronización se usa en el método run ():
// Este programa utiliza un bloque sincronizado. Class CallMe {Void Call (String Msg) {System.out.print ("[" + msg); out.println ("interrumpido"); ; ; "); Caller ob3 = new Caller (objetivo," Mundo "); // Espere a que los subprocesos terminen try {ob1.t.Join (); ob2.t.Join (); ob3.t.join ();} Catch (InterruptedException e) {System.out.println ("Interrupted");
Aquí, el método de llamada () no se modifica sincronizado. Sincronized se declara en el método run () de la clase de llamadas. Esto da el mismo resultado correcto en el ejemplo anterior, porque cada hilo espera el final del hilo anterior antes de que se ejecute.
Java Inter-Thread Communication <Br /> múltiple reemplaza los programas de bucle de eventos dividiendo tareas en unidades discretas y lógicas. Los hilos tienen una segunda ventaja: se mantiene alejado de las encuestas. La encuesta generalmente se logra mediante un bucle que repite las condiciones de monitoreo. Una vez que se establecen las condiciones, se deben tomar medidas apropiadas. Esto desperdicia tiempo de CPU. Por ejemplo, considere el problema de secuencia clásica cuando un hilo está generando datos mientras otro programa lo consume. Para que el problema sea más interesante, suponga que el generador de datos debe esperar a que el consumidor finalice el trabajo antes de que pueda generar nuevos datos. En un sistema de votación, los consumidores desperdician muchos ciclos de CPU mientras esperan que el productor genere datos. Una vez que el productor complete el trabajo, comenzará a sondear, perdiendo más tiempo de CPU esperando que el trabajo del consumidor termine, así que. Obviamente, esta situación no es popular.
Para evitar la encuesta, Java incluye un mecanismo de comunicación entre procesos implementado a través de los métodos Wait (), Notify () y Notifyall (). Estos métodos se implementan en objetos con métodos finales, por lo que todas las clases los contienen. Estos tres métodos solo pueden llamarse en el método sincronizado. Aunque estos métodos son muy avanzados en concepto desde la perspectiva de la visión de la informática, son muy simples de usar en la práctica:
Wait () le dice a la rosca llamada que renuncie a la tubería y vaya a dormir hasta que otros roscas ingresen a la misma tubería y llame a notificar ().
notificar () restaura el primer hilo en el mismo objeto para llamar a Wait ().
NotifyAll () restaura todos los hilos en el mismo objeto que llama Wait (). El hilo con la prioridad más alta funciona primero.
Estos métodos se declaran en el objeto de la siguiente manera:
Final void wait () lanza interruptedException Final Void notify () Final Void notifyall ()
Existe otra forma de espera () le permite definir el tiempo de espera.
Los siguientes errores del programa de ejemplo implementan un simple problema de productor/consumidor. Consiste en cuatro clases: Q, que logra obtener una secuencia sincronizada;
// una implementación incorrecta de un productor y consumidor. Class Q {int n; this .n = n; ) .Start (); = Q; ) {Q q = nuevo Q ();
Aunque los métodos put () y get () en la clase Q están sincronizados, nada impide que el productor supere al consumidor, y nada impide que el consumidor consuma la misma secuencia dos veces. De esta manera, obtiene la siguiente salida de error (la salida cambiará con la velocidad del procesador y las tareas cargadas):
PUT: 1GOT: 1 GOT: 1 GOT: 1 GOT: 1 GOT: 1PUT: 2PUT: 3PUT: 4PUT: 5PUT: 6PUT: 7GOT: 7
Después de que el productor genera 1, el consumidor obtiene las mismas 15 veces a su vez. Los productores continúan generando 2 a 7, y los consumidores no tienen oportunidad de obtenerlos.
Para escribir correctamente este programa en Java, use Wait () y notify () para marcar ambas direcciones, como se muestra a continuación:
// una implementación correcta de un productor y consumidor. Class Q {int n; . Wait (); ); While (true) {Q.put (i ++); public void run () {while (true) {q.get (); (Q);
Se llama al interno get (), Wait (). Esto cuelga la ejecución hasta que el productor informa los datos que está listo. En este momento, se reanuda el Get () interno. Después de obtener los datos, obtenga () llamadas notify (). Esto le dice al productor que puede ingresar más datos en la secuencia. En put (), Wait () suspende la ejecución hasta que el consumidor quita el elemento en la secuencia. Cuando la ejecución continúa, el siguiente elemento de datos se coloca en la secuencia y se llama a notificar (), lo que notifica al consumidor que debe eliminar los datos.
Aquí está el resultado del programa, que muestra claramente el comportamiento de sincronización:
PUT: 1 GOT: 1PUT: 2GOT: 2PUT: 3GOT: 3PUT: 4GOT: 4PUT: 5GOT: 5