Sincronización de métodos y bloques en Java

Los subprocesos se comunican principalmente compartiendo el acceso a los campos y los campos de referencia de objetos a los que se refieren. Esta forma de comunicación es extremadamente eficiente, pero hace posibles dos tipos de errores: interferencia de subprocesos y errores de consistencia de la memoria. Se necesitan algunas construcciones de sincronización para evitar estos errores. El siguiente ejemplo muestra una situación en la que necesitamos sincronización.

Necesidad de Sincronización

Considere el siguiente ejemplo:

// Java program to illustrate need
// of Synchronization
import java.io.*;
  
class Multithread
{
    private int i = 0;
    public void increment()
    {
        i++;
    }
  
    public int getValue()
    {
        return i;
    }
}
  
class GfG
{
    public static void main (String[] args)
    {
        Multithread t = new Multithread();
        t.increment();
        System.out.println(t.getValue());
    }
}

Producción:

1

En el ejemplo anterior se realizan tres operaciones:

  1. Obtener el valor de la variable i.
  2. Incrementa el valor obtenido.
  3. Y almacene el valor aumentado de i en su ubicación.

Aquí,

  • El primer subproceso obtiene el valor de i. (Actualmente el valor i es 0) y lo aumenta en uno, por lo que el valor de la variable i se convierte en 1.
  • Ahora el segundo subproceso accede al valor de i que sería 0 ya que el primer subproceso no lo almacenó en su ubicación.
    Y el segundo hilo también lo incrementa y lo almacena en su ubicación. Y 1º también almacenarlo.
  • Finalmente el valor de la variable i es 1. Pero debería ser 2 por el efecto de ambos hilos. Es por eso que necesitamos sincronizar el acceso a la variable compartida i.

Java es un lenguaje de subprocesos múltiples donde varios subprocesos se ejecutan en paralelo para completar su ejecución. Necesitamos sincronizar los recursos compartidos para garantizar que a la vez solo un subproceso pueda acceder al recurso compartido.
Si un objeto es compartido por varios subprocesos, es necesario sincronizarlo para evitar que el estado del objeto se corrompa. La sincronización es necesaria cuando el objeto es mutable. Si el Objeto compartido es inmutable o todos los subprocesos que comparten el mismo Objeto solo leen el estado del Objeto sin modificarlo, entonces no necesita sincronizarlo.

El lenguaje de programación Java proporciona dos modismos de sincronización:

  • Sincronización de métodos
  • Sincronización de sentencias (sincronización de bloques)

Sincronización de métodos

Los métodos sincronizados permiten una estrategia simple para evitar la interferencia de subprocesos y los errores de consistencia de la memoria. Si un objeto es visible para más de un subproceso, todas las lecturas o escrituras en los campos de ese objeto se realizan a través del método sincronizado .

No es posible que se intercalen dos invocaciones para métodos sincronizados. Si un subproceso está ejecutando el método sincronizado, todos los demás subprocesos que invocan el método sincronizado en el mismo Objeto tendrán que esperar hasta que el primer subproceso termine con el Objeto.

Ejemplo: Esto muestra si más de un subproceso accede al método getLine() sin sincronización.

// Example illustrates multiple threads are executing
// on the same Object at same time without synchronization.
import java.io.*;
  
class Line
{
    // if multiple threads(trains) will try to
    // access this unsynchronized method,
    // they all will get it. So there is chance
    // that Object's  state will be corrupted.
    public void getLine()
    {
        for (int i = 0; i < 3; i++)
        {
            System.out.println(i);
            try
            {
                Thread.sleep(400);
            }
            catch (Exception e)
            {
                System.out.println(e);
            }
        }
    }
}
  
class Train extends Thread
{
    // reference to Line's Object.
    Line line;
  
    Train(Line line)
    {
        this.line = line;
    }
  
    @Override
    public void run()
    {
        line.getLine();
    }
}
  
class GFG
{
    public static void main(String[] args)
    {
        // Object of Line class that is shared
        // among the threads.
        Line obj = new Line();
  
        // creating the threads that are
        // sharing the same Object.
        Train train1 = new Train(obj);
        Train train2 = new Train(obj);
  
        // threads start their execution.
        train1.start();
        train2.start();
    }
}

Producción

0
0
1
1
2
2

Puede haber dos trenes (más de dos) que necesitan usar el mismo al mismo tiempo, por lo que existe la posibilidad de colisión. Por lo tanto, para evitar la colisión, debemos sincronizar la línea en la que varios quieren correr.

Ejemplo: acceso sincronizado al método getLine() en el mismo objeto

// Example that shows multiple threads
// can execute the same method but in
// synchronized way.
class Line
{
  
    // if multiple threads(trains) trying to access
    // this synchronized method on the same Object
    // but only one thread will be able
    // to execute it at a time.
    synchronized public void getLine()
    {
        for (int i = 0; i < 3; i++)
        {
            System.out.println(i);
            try
            {
                Thread.sleep(400);
            }
            catch (Exception e)
            {
                System.out.println(e);
            }
        }
    }
}
  
class Train extends Thread
{
    // Reference variable of type Line.
    Line line;
  
    Train(Line line)
    {
        this.line = line;
    }
  
    @Override
    public void run()
    {
        line.getLine();
    }
}
  
class GFG
{
    public static void main(String[] args)
    {
        Line obj = new Line();
  
        // we are creating two threads which share
        // same Object.
        Train train1 = new Train(obj);
        Train train2 = new Train(obj);
  
        // both threads start executing .
        train1.start();
        train2.start();
    }
}

Producción:

0
1
2
0
1
2

Sincronización de bloques

Si solo necesitamos ejecutar algunas líneas de código subsiguientes, no todas las líneas (instrucciones) de código dentro de un método, entonces debemos sincronizar solo el bloque de código dentro del cual existen las instrucciones requeridas.
Por ejemplo, supongamos que hay un método que contiene 100 líneas de código pero solo hay 10 líneas (una tras otra) de código que contienen una sección crítica de código, es decir, estas líneas pueden modificar (cambiar) el estado del Objeto. Por lo tanto, solo necesitamos sincronizar estas 10 líneas de método de código para evitar cualquier modificación en el estado del Objeto y asegurarnos de que otros subprocesos puedan ejecutar el resto de las líneas dentro del mismo método sin ninguna interrupción.

import java.io.*;
import java.util.*;
  
public class Geek
{
    String name = "";
    public int count = 0;
  
    public void geekName(String geek, List<String> list)
    {
        // Only one thread is permitted
        // to change geek's name at a time.
        synchronized(this)
        {
            name = geek;
            count++;  // how many threads change geek's name.
        }
  
        // All other threads are permitted
        // to add geek name into list.
        list.add(geek);
    }
}
  
class GFG
{
    public static void main (String[] args)
    {
        Geek gk = new Geek();
        List<String> list = new ArrayList<String>();
        gk.geekName("mohit", list);
        System.out.println(gk.name);
  
    }
}

Producción :

mohit

Puntos importantes:

  • Cuando un subproceso entra en un método o bloque sincronizado, adquiere el bloqueo y una vez que completa su tarea y sale del método sincronizado, libera el bloqueo.
  • Cuando el subproceso ingresa en el método o bloque de instancia sincronizada, adquiere el bloqueo de nivel de objeto y cuando ingresa en el método o bloque estático sincronizado, adquiere el bloqueo de nivel de clase.
  • La sincronización de Java generará una excepción de puntero nulo si el objeto utilizado en el bloque sincronizado es nulo. Por ejemplo, si en sincronizado (instancia) , la instancia es nula, arrojará una excepción de puntero nulo.
  • En Java, esperar(), notificar() y notificarTodos() son los métodos importantes que se utilizan en la sincronización.
  • No puede aplicar la palabra clave java sincronizada con las variables.
  • No sincronice en el campo no final en el bloque sincronizado porque la referencia al campo no final puede cambiar en cualquier momento y luego diferentes subprocesos pueden sincronizarse en diferentes objetos, es decir, no hay sincronización en absoluto.

Ventajas

  • Subprocesos múltiples: dado que Java es un lenguaje de subprocesos múltiples, la sincronización es una buena manera de lograr la exclusión mutua en los recursos compartidos.
  • Métodos de instancia y estáticos: tanto los métodos de instancia sincronizados como los métodos estáticos sincronizados se pueden ejecutar simultáneamente porque se utilizan para bloquear diferentes objetos.

Limitaciones

  • Limitaciones de simultaneidad: la sincronización de Java no permite lecturas simultáneas.
  • Disminuye la eficiencia: el método sincronizado de Java se ejecuta muy lentamente y puede degradar el rendimiento, por lo que debe sincronizar el método cuando sea absolutamente necesario, de lo contrario no y sincronizar el bloque solo para la sección crítica del código.

Este artículo es una contribución de Nitsdheerendra . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo usando contribuya.geeksforgeeks.org o envíe su artículo por correo a contribuya@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *