CodeGym /Blog Java /Random-ES /Sincronización de hilos. El operador sincronizado
Autor
Jesse Haniel
Lead Software Architect at Tribunal de Justiça da Paraíba

Sincronización de hilos. El operador sincronizado

Publicado en el grupo Random-ES
¡Hola! Hoy continuaremos considerando las características de la programación de subprocesos múltiples y hablaremos sobre la sincronización de subprocesos. Sincronización de hilos.  El operador sincronizado - 1

¿Qué es la sincronización en Java?

Fuera del dominio de la programación, implica un arreglo que permite que dos dispositivos o programas funcionen juntos. Por ejemplo, un teléfono inteligente y una computadora se pueden sincronizar con una cuenta de Google, y la cuenta de un sitio web se puede sincronizar con cuentas de redes sociales para que pueda usarlas para iniciar sesión. La sincronización de subprocesos tiene un significado similar: es un arreglo en el que los subprocesos interactúan con entre sí. En lecciones anteriores, nuestros subprocesos vivían y trabajaban por separado. Uno realizó un cálculo, un segundo durmió y un tercero mostró algo en la consola, pero no interactuaron. En programas reales, tales situaciones son raras. Múltiples subprocesos pueden trabajar activamente con el mismo conjunto de datos y modificarlo. Esto crea problemas. Imagine varios subprocesos escribiendo texto en el mismo lugar, por ejemplo, en un archivo de texto o en la consola. En este caso, el archivo o la consola se convierte en un recurso compartido. Los subprocesos desconocen la existencia de los demás, por lo que simplemente escriben todo lo que pueden en el tiempo que les asigna el programador de subprocesos. En una lección reciente, vimos un ejemplo de a dónde lleva esto. Recordémoslo ahora: Sincronización de hilos.  El operador sincronizado - 2La razón radica en el hecho de que los hilos están trabajando con un recurso compartido (la consola) sin coordinar sus acciones entre sí. Si el programador de subprocesos asigna tiempo a Subproceso-1, instantáneamente escribe todo en la consola. Lo que otros subprocesos hayan logrado escribir o no, no importa. El resultado, como podéis ver, es deprimente. Es por eso que introdujeron un concepto especial, el mutex (exclusión mutua) , a la programación multiproceso. El propósito de un mutexes proporcionar un mecanismo para que solo un hilo tenga acceso a un objeto en un momento determinado. Si Thread-1 adquiere el mutex del objeto A, los otros hilos no podrán acceder ni modificar el objeto. Los otros subprocesos deben esperar hasta que se libere el mutex del objeto A. Aquí hay un ejemplo de la vida: imagina que tú y otros 10 extraños están participando en un ejercicio. Por turnos, necesita expresar sus ideas y discutir algo. Pero debido a que se están viendo por primera vez, para no interrumpirse constantemente y enfurecerse, usan una 'pelota parlante': solo la persona con la pelota puede hablar. De esta manera terminas teniendo una buena y fructífera discusión. Esencialmente, la pelota es un mutex. Si el mutex de un objeto está en manos de un subproceso, otros subprocesos no pueden trabajar con el objeto.Objectclase, lo que significa que cada objeto en Java tiene uno.

Cómo funciona el operador sincronizado

Conozcamos una nueva palabra clave: sincronizado . Se utiliza para marcar un determinado bloque de código. Si un bloque de código está marcado con la synchronizedpalabra clave, entonces ese bloque solo puede ser ejecutado por un subproceso a la vez. La sincronización se puede implementar de diferentes maneras. Por ejemplo, al declarar un método completo para sincronizar:

public synchronized void doSomething() {

   // ...Method logic
}
O escriba un bloque de código donde la sincronización se realice utilizando algún objeto:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...Some logic available simultaneously to all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
El significado es simple. Si un subproceso ingresa al bloque de código marcado con la synchronizedpalabra clave, instantáneamente captura la exclusión mutua del objeto, y todos los demás subprocesos que intentan ingresar al mismo bloque o método se ven obligados a esperar hasta que el subproceso anterior complete su trabajo y libere el monitor. Sincronización de hilos.  El operador sincronizado - 3¡Por cierto! Durante el curso, ya has visto ejemplos de synchronized, pero se veían diferentes:

public void swap()
{
   synchronized (this)
   {
       // ...Method logic
   }
}
El tema es nuevo para ti. Y, por supuesto, habrá confusión con la sintaxis. Por lo tanto, memorícelo de inmediato para evitar confundirse más tarde con las diferentes formas de escribirlo. Estas dos formas de escribirlo significan lo mismo:

public void swap() {

   synchronized (this)
   {
       // ...Method logic
   }
}


public synchronized void swap() {

   }
}
En el primer caso, debe crear un bloque de código sincronizado inmediatamente después de ingresar el método. Está sincronizado por el thisobjeto, es decir, el objeto actual. Y en el segundo ejemplo, aplica la synchronizedpalabra clave a todo el método. Esto hace que no sea necesario indicar explícitamente el objeto que se utiliza para la sincronización. Dado que todo el método está marcado con la palabra clave, el método se sincronizará automáticamente para todas las instancias de la clase. No nos sumergiremos en una discusión sobre qué camino es mejor. Por ahora, elija la forma que más le guste :) Lo principal es recordar: puede declarar un método sincronizado solo cuando toda su lógica se ejecuta en un hilo a la vez. Por ejemplo, sería un error sincronizar el siguiente doSomething()método:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...Some logic available simultaneously to all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
Como puede ver, parte del método contiene lógica que no requiere sincronización. Ese código puede ser ejecutado por varios subprocesos al mismo tiempo, y todos los lugares críticos se separan en un synchronizedbloque separado. Y una cosa más. Examinemos de cerca nuestro ejemplo de la lección con intercambio de nombres:

public void swap()
{
   synchronized (this)
   {
       // ...Method logic
   }
}
Nota: la sincronización se realiza mediantethis. Es decir, utilizando unMyClassobjeto específico. Supongamos que tenemos 2 hilos (Thread-1yThread-2) y solo unMyClass myClassobjeto. En este caso, siThread-1llama almyClass.swap()método, el mutex del objeto estará ocupado y, cuando intente llamar, elmyClass.swap()métodoThread-2se bloqueará mientras espera que se libere el mutex. Si vamos a tener 2 subprocesos y 2MyClassobjetos (myClass1ymyClass2), nuestros subprocesos pueden fácilmente ejecutar simultáneamente los métodos sincronizados en diferentes objetos. El primer hilo ejecuta esto:

myClass1.swap();
El segundo ejecuta esto:

myClass2.swap();
En este caso, la synchronizedpalabra clave dentro del swap()método no afectará el funcionamiento del programa, ya que la sincronización se realiza mediante un objeto específico. Y en este último caso, tenemos 2 objetos. Por lo tanto, los subprocesos no crean problemas entre sí. Después de todo, dos objetos tienen 2 mutex diferentes, y adquirir uno es independiente de adquirir el otro .

Particularidades de la sincronización en métodos estáticos

Pero, ¿qué sucede si necesita sincronizar un método estático ?

class MyClass {
   private static String name1 = "Ally";
   private static String name2 = "Lena";

   public static synchronized void swap() {
       String s = name1;
       name1 = name2;
       name2 = s;
   }

}
No está claro qué papel jugará el mutex aquí. Después de todo, ya determinamos que cada objeto tiene un mutex. Pero el problema es que no necesitamos objetos para llamar al MyClass.swap()método: ¡el método es estático! ¿Qué es lo siguiente? :/ En realidad no hay problema aquí. Los creadores de Java se encargaron de todo :) Si un método que contiene lógica concurrente crítica es estático, entonces la sincronización se realiza a nivel de clase. Para mayor claridad, podemos reescribir el código anterior de la siguiente manera:

class MyClass {
   private static String name1 = "Ally";
   private static String name2 = "Lena";

   public static void swap() {

       synchronized (MyClass.class) {
           String s = name1;
           name1 = name2;
           name2 = s;
       }
   }

}
En principio, podría haber pensado en esto usted mismo: debido a que no hay objetos, el mecanismo de sincronización debe integrarse de alguna manera en la clase misma. Y así es: podemos usar clases para sincronizar.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION