JavaRush /Blog Java /Random-ES /Sincronización de hilos. Operador sincronizado en Java

Sincronización de hilos. Operador sincronizado en Java

Publicado en el grupo Random-ES
¡Hola! Hoy continuaremos considerando las características de la programación multiproceso y hablaremos sobre la sincronización de subprocesos.
Sincronización de hilos.  Operador sincronizado - 1
¿Qué es la “sincronización”? Fuera del ámbito de la programación, esto se refiere a algún tipo de configuración 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 una cuenta personal en un sitio web se puede sincronizar con cuentas en las redes sociales para iniciar sesión con ellas. La sincronización de subprocesos tiene un significado similar: configura cómo interactúan los subprocesos entre sí. En conferencias anteriores, nuestros hilos vivían y trabajaban por separado unos de otros. Uno contaba algo, el segundo dormía, el tercero mostraba algo en la consola, pero no interactuaban entre sí. En programas reales, estas situaciones son raras. Varios hilos pueden trabajar activamente, por ejemplo, con el mismo conjunto de datos y cambiar algo en él. Esto crea problemas. Imagine que varios subprocesos escriben texto en la misma ubicación, como un archivo de texto o la consola. Este archivo o consola en este caso se convierte en un recurso compartido. Los subprocesos no conocen la existencia de los demás, por lo que simplemente escriben todo lo que pueden gestionar en el tiempo que les asigna el programador de subprocesos. En una conferencia reciente del curso tuvimos un ejemplo de a qué conduciría esto, recordémoslo: Sincronización de hilos.  Operador sincronizado - 2la razón radica en que los hilos trabajaban con un recurso compartido, la consola, sin coordinar acciones entre sí. Si el programador de subprocesos ha asignado tiempo al subproceso-1, inmediatamente escribe todo en la consola. Lo que otros hilos ya han logrado escribir o no han logrado escribir no es importante. El resultado, como puedes ver, es desastroso. Por lo tanto, en la programación multiproceso, se introdujo un concepto especial de exclusión mutua (del inglés "mutex", "exclusión mutua" - "exclusión mutua") . El propósito de un mutex es proporcionar un mecanismo para que solo un hilo tenga acceso a un objeto en un momento determinado. Si Thread-1 ha adquirido el mutex del objeto A, otros hilos no tendrán acceso a él para cambiar nada en él. Hasta que se libere el mutex del objeto A, los subprocesos restantes se verán obligados a esperar. Ejemplo de la vida real: imagina que tú y otros 10 desconocidos estáis participando en una formación. Deben turnarse para expresar ideas y discutir algo. Pero, como se ven por primera vez, para no interrumpirse constantemente y no caer en un alboroto, se utiliza la regla de la "pelota parlante": solo una persona puede hablar: la que tiene la pelota en sus manos. De esta manera la discusión resulta adecuada y fructífera. Entonces, un mutex, en esencia, es una bola de este tipo. Si el mutex de un objeto está en manos de un hilo, otros hilos no podrán acceder al objeto. No necesitas hacer nada para crear un mutex: ya está integrado en la clase Object, lo que significa que todos los objetos en Java lo tienen.

Cómo funciona el operador sincronizado en Java

Conozcamos una nueva palabra clave: sincronizado . Marca una determinada parte de nuestro código. Si un bloque de código está marcado con la palabra clave sincronizada, significa que el bloque solo puede ser ejecutado por un subproceso a la vez. La sincronización se puede implementar de diferentes maneras. Por ejemplo, cree un método sincronizado completo:
public synchronized void doSomething() {

   //...método lógico
}
O escriba un bloque de código donde se realice la sincronización sobre algún objeto:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //... algo de lógica disponible para todos los subprocesos

       synchronized (obj) {

           //lógica que solo está disponible para un subproceso a la vez
       }
   }
}
El significado es simple. Si un subproceso ingresa a un bloque de código marcado con la palabra sincronizado, adquiere instantáneamente el mutex 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.  Operador sincronizado - 3¡Por cierto! En las conferencias del curso ya has visto ejemplos de sincronizados, pero se veían diferentes:
public void swap()
{
   synchronized (this)
   {
       //...método lógico
   }
}
El tema es nuevo para usted y, por supuesto, al principio habrá confusión con la sintaxis. Por lo tanto, recuerde de inmediato para no confundirse más adelante en los métodos de escritura. Estos dos métodos de escritura significan lo mismo:
public void swap() {

   synchronized (this)
   {
       //...método lógico
   }
}


public synchronized void swap() {

   }
}
En el primer caso, crea un bloque de código sincronizado inmediatamente después de ingresar el método. Se sincroniza por objeto this, es decir, por el objeto actual. Y en el segundo ejemplo pones la palabra sincronizado en todo el método. Ya no es necesario indicar explícitamente ningún objeto sobre el que se realiza la sincronización. Una vez que un método completo está marcado con una palabra, este método se sincronizará automáticamente para todos los objetos de la clase. No profundicemos en discusiones sobre qué método es mejor. Por ahora, elige lo que más te guste :) Lo principal es recordar: puedes declarar un método sincronizado solo cuando toda la lógica que contiene es ejecutada por un hilo al mismo tiempo. Por ejemplo, en este caso doSomething()sería un error sincronizar el método:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //... algo de lógica disponible para todos los subprocesos

       synchronized (obj) {

           //lógica que solo está disponible para un subproceso a la vez
       }
   }
}
Como puede ver, una parte del método contiene lógica para la cual no se requiere sincronización. El código que contiene puede ser ejecutado por varios subprocesos simultáneamente y todos los lugares críticos se asignan a un bloque sincronizado separado. Y un momento. Miremos bajo el microscopio nuestro ejemplo de la conferencia con el intercambio de nombres:
public void swap()
{
   synchronized (this)
   {
       //...método lógico
   }
}
Tenga en cuenta: la sincronización se realiza mediante this. Es decir, para un objeto específico MyClass. Imaginemos que tenemos 2 hilos ( Thread-1y Thread-2) y solo un objeto MyClass myClass. En este caso, si Thread-1se llama al método myClass.swap(), el mutex del objeto estará ocupado y Thread-2cuando intente llamarlo, myClass.swap()se bloqueará esperando a que el mutex quede libre. Si tenemos 2 subprocesos y 2 objetos MyClass, myClass1y myClass2, en diferentes objetos, nuestros subprocesos pueden ejecutar fácilmente métodos sincronizados simultáneamente. El primer hilo hace:
myClass1.swap();
El segundo hace:
myClass2.swap();
En este caso, la palabra clave sincronizada dentro del método swap()no afectará el funcionamiento del programa, ya que la sincronización se realiza sobre un objeto específico. Y en el último caso tenemos 2 objetos, por lo que los hilos no se crean problemas entre sí. Después de todo, dos objetos tienen 2 mutex diferentes y su captura no depende el uno del otro.

Características de la sincronización en métodos estáticos.

Pero ¿qué pasa si necesitas sincronizar un método estático?
class MyClass {
   private static String name1 = "Olia";
   private static String name2 = "lena";

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

}
No está claro qué servirá como mutex en este caso. Después de todo, ya hemos decidido que cada objeto tiene un mutex. Pero el problema es que para llamar a un método estático MyClass.swap()no necesitamos objetos: ¡el método es estático! Entonces, ¿qué sigue? :/ En realidad, no hay ningún problema con esto. Los creadores de Java se encargaron de todo :) Si el método que contiene la lógica “multithreaded” crítica es estático, la sincronización se realizará por clases. Para mayor claridad, el código anterior se puede reescribir como:
class MyClass {
   private static String name1 = "Olia";
   private static String name2 = "lena";

   public static void swap() {

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

}
En principio, podrías haber pensado en esto por tu cuenta: dado que no hay objetos, entonces el mecanismo de sincronización debe estar de alguna manera "cableado" en las clases mismas. Así es: también puedes sincronizar entre clases.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION