Importancia de la sincronización de subprocesos en Java

Nuestros sistemas funcionan en un entorno de subprocesos múltiples que se convierte en una parte importante del sistema operativo para proporcionar una mejor utilización de los recursos. El proceso de ejecutar dos o más partes del programa simultáneamente se conoce como Multithreading. Un programa es un conjunto de instrucciones en las que se ejecutan múltiples procesos y, dentro de un proceso, funcionan múltiples subprocesos. Los hilos no son más que procesos ligeros. Por ejemplo, en la computadora estamos jugando videojuegos al mismo tiempo que estamos trabajando con MS Word y escuchando música. Entonces, estos son los procesos en los que estamos trabajando simultáneamente. En esto, cada aplicación tiene múltiples subprocesos, es decir, subprocesos. En el ejemplo anterior, escuchamos música en la que tenemos un reproductor de música como una aplicación que contiene múltiples subprocesos que se ejecutan como administrar la lista de reproducción,  

Esta es la introducción básica de subprocesos múltiples que ayudará aún más a comprender la importancia de la sincronización de subprocesos.

Prioridades de subprocesos

En java, a cada subproceso se le asigna una prioridad que determina cómo se deben tratar los subprocesos entre sí. La prioridad del subproceso se utiliza para decidir cuándo cambiar de un subproceso en ejecución al siguiente. Un subproceso de mayor prioridad puede adelantarse a un subproceso de menor prioridad y puede requerir más tiempo de CPU. De manera simple, un hilo con mayor prioridad obtiene el recurso primero en comparación con el hilo con menor prioridad. Pero, en caso de que dos subprocesos con la misma prioridad quieran el mismo recurso, la situación se vuelve más complicada. Entonces, en un entorno de subprocesos múltiples, si los subprocesos con la misma prioridad están trabajando con el mismo recurso, se obtienen resultados no deseados o código erróneo.

Tomemos un ejemplo. En una habitación, tenemos varias computadoras conectadas a una sola impresora. En un momento, una computadora quiere imprimir un documento, por lo que usa una impresora. Al mismo tiempo, otra computadora quiere que la impresora imprima su documento. Entonces, dos computadoras exigen el mismo recurso, es decir, una impresora. Entonces, si ambos procesos se ejecutan juntos, la impresora imprimirá el documento de una y otra computadora. Esto producirá una salida no válida. Ahora, lo mismo sucede en el caso de los subprocesos si dos subprocesos con la misma prioridad o que desean el mismo recurso generan resultados inconsistentes.

En Java, cuando dos o más subprocesos intentan acceder al mismo recurso simultáneamente, el tiempo de ejecución de Java ejecuta uno o más subprocesos lentamente, o incluso suspende su ejecución. Para superar este problema, tenemos sincronización de subprocesos. 

Sincronización significa coordinación entre múltiples procesos/hilos.  

Tipos de sincronización:

Existen dos tipos de sincronización que son los siguientes:

  1. Sincronización de procesos
  2. Sincronización de subprocesos

Aquí nos centraremos principalmente en la sincronización de subprocesos. 

La sincronización de subprocesos se refiere básicamente al concepto de ejecución de un subproceso a la vez y el resto de los subprocesos están en estado de espera . Este proceso se conoce como sincronización de subprocesos. Previene la interferencia del hilo y el problema de inconsistencia.

La sincronización se construye usando bloqueos o monitores . En Java, un monitor es un objeto que se usa como un candado mutuamente excluyente. Solo un único subproceso a la vez tiene derecho a poseer un monitor. Cuando un subproceso obtiene un bloqueo, todos los demás subprocesos se suspenderán y están tratando de adquirir el monitor bloqueado. Entonces, se dice que otros subprocesos están esperando al monitor, hasta que el primer subproceso sale del monitor. De una manera simple, cuando un subproceso solicita un recurso, ese recurso se bloquea para que ningún otro subproceso pueda funcionar o realizar ninguna modificación hasta que se libere el recurso.

La sincronización de subprocesos es de dos tipos:

  1. Exclusivo mutuo
  2. Comunicación entre subprocesos

A. Mutua Exclusividad

Al compartir cualquier recurso, esto mantendrá el hilo interfiriendo entre sí, es decir, excluyendo mutuamente. Podemos lograr esto a través de

  • Método sincronizado
  • Bloque sincronizado
  • Sincronización estática

Método sincronizado

Podemos declarar un método como sincronizado usando la palabra clave «sincronizado» . Esto hará que el código escrito dentro del método sea seguro para subprocesos, de modo que ningún otro subproceso se ejecute mientras se comparte el recurso.

Implementación:

Estaremos proponiendo impresiones de los dos subprocesos simultáneamente mostrando el comportamiento asincrónico sin sincronización de subprocesos. 

Ejemplo 1:

Java

// Class 1
// Helper class
// Extending Thread class
public class PrintTest extends Thread {
 
    // Non synchronized Code
 
    // Method 1
    public void printThread(int n)
    {
 
        // This loop will print the  currently executed
        // thread
        for (int i = 1; i <= 10; i++) {
            System.out.println("Thread " + n
                               + " is working...");
 
            // Try block to check for exceptions
            try {
 
                // Pause the execution of current thread
                // for 0.600 seconds using sleep() method
                Thread.sleep(600);
            }
 
            // Catch block to handle the exceptions
            catch (Exception ex) {
 
                // Overriding existing toString() method and
                // prints exception if occur
                System.out.println(ex.toString());
            }
        }
 
        // Display message for better readability
        System.out.println("--------------------------");
 
        try {
 
            // Pause the execution of current  thread
            // for 0.1000 millisecond or 1sec using sleep
            // method
            Thread.sleep(1000);
        }
 
        catch (Exception ex) {
 
            // Printing the exception
            System.out.println(ex.toString());
        }
    }
}
 
// Class 2
// Helper class extending Thread Class
public class Thread1 extends Thread {
 
    // Declaring variable of type Class1
    PrintTest test;
 
    // Constructor for class1
    Thread1(PrintTest p) { test = p; }
 
    // run() method of this class for
    // entry point for thread1
    public void run()
    {
 
        // Calling method  1 as in above class
        test.printThread(1);
    }
}
 
// Class 3
// Helper class extending Thread Class
public class Thread2 extends Thread {
 
    // Declaring variable of type Class1
    PrintTest test;
 
    // Constructor for class2
    Thread2(PrintTest p) { test = p; }
 
    // run() method of this class for
    // entry point for thread2
    public void run() { test.printThread(2); }
}
 
// Class 4
// Main class
public class SynchroTest {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // Creating object of class 1 inside main() method
        PrintTest p = new PrintTest();
 
        // Passing the same object of class PrintTest to
        // both threads
        Thread1 t1 = new Thread1(p);
        Thread2 t2 = new Thread2(p);
 
        // Start executing the threads
        // using start() method
        t1.start();
        t2.start();
 
        // This will print both the threads  simultaneously
    }
}

Producción: 

Ahora, al usar el método sincronizado, bloqueará el objeto para el recurso compartido y brindará un resultado consistente. 

Ejemplo 2: 

Java

// Java Program Illustrating Lock the Object for
// the shared resource giving consistent output
 
// Class 1
// Helper class extending Thread class
public class PrintTest extends Thread {
 
    // synchronized code
    // synchronized method will lock the object and
    // releases when thread is terminated or completed its
    // execution.
    synchronized public void printThread(int n)
    {
        for (int i = 1; i <= 10; i++) {
            System.out.println("Thread " + n
                               + " is working...");
 
            try {
 
                // pause the execution of current  thread
                // for 600 millisecond
                Thread.sleep(600);
            }
            catch (Exception ex) {
                // overrides toString() method  and prints
                // exception if occur
                System.out.println(ex.toString());
            }
        }
        System.out.println("--------------------------");
        try {
 
            // pause the execution of current  thread for
            // 1000 millisecond
            Thread.sleep(1000);
        }
        catch (Exception ex) {
            System.out.println(ex.toString());
        }
    }
}
// creating thread1 extending Thread Class
 
public class Thread1 extends Thread {
 
    PrintTest test;
    Thread1(PrintTest p) { test = p; }
 
    public void run() // entry point for thread1
    {
 
        test.printThread(1);
    }
}
// creating thread2 extending Thread Class
 
public class Thread2 extends Thread {
 
    PrintTest test;
    Thread2(PrintTest p) { test = p; }
    public void run() // entry point for thread2
    {
        test.printThread(2);
    }
}
 
public class SynchroTest {
 
    public static void main(String[] args)
    {
 
        PrintTest p = new PrintTest();
       
        // passing the same object of class PrintTest to
        // both threads
        Thread1 t1 = new Thread1(p);
        Thread2 t2 = new Thread2(p);
       
        // start function will execute the threads
        t1.start();
        t2.start();
    }
}

Producción:

B. Bloque sincronizado

Si declaramos un bloque como sincronizado, solo el código que está escrito dentro de ese bloque se ejecuta secuencialmente, no el código completo. Esto se usa cuando queremos acceso secuencial a alguna parte del código o para sincronizar alguna parte del código.

Sintaxis:

synchronized (object reference) 
{    
   // Insert code here
}

Ejemplo 

Java

// Java Program Illustrating  Synchronized Code
// Using synchronized block
 
// Class 1
// Helper class extending Thread class
class PrintTest extends Thread {
 
    // Method  1
    // To print the thread
    public void printThread(int n)
    {
 
        // Making synchronized block that makes the block
        // synchronized
        synchronized (this)
        {
 
            // Iterating using for loop
            for (int i = 1; i <= 10; i++) {
 
                // Print message when these thread are
                // executing
                System.out.println("Thread " + n
                                   + " is working...");
 
                // Try block to check for exceptions
                try {
 
                    // Making thread to pause for 0.6
                    // seconds
                    Thread.sleep(600);
                }
 
                // Catch block to handle exceptions
                catch (Exception ex) {
 
                    // Print message when exception.s occur
                    System.out.println(ex.toString());
                }
            }
        }
 
        // Display message only
        System.out.println("--------------------------");
 
        try {
 
            // Making thread t osleep for 1 sec
            Thread.sleep(1000);
        }
 
        catch (Exception ex) {
 
            System.out.println(ex.toString());
        }
    }
}
 
// Class 2
// Helper class extending Thread class
class Thread1 extends Thread {
 
    PrintTest test;
    Thread1(PrintTest p) { test = p; }
 
    public void run() { test.printThread(1); }
}
 
// Class 3
// Helper class extending Thread class
class Thread2 extends Thread {
 
    PrintTest test;
    Thread2(PrintTest p) { test = p; }
 
    public void run() { test.printThread(2); }
}
 
// Class 4
// Main class
class SynchroTest {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // Creating instance for class 1 inside main()
        PrintTest p = new PrintTest();
 
        // Creating threads and
        // passing same object
        Thread1 t1 = new Thread1(p);
        Thread2 t2 = new Thread2(p);
 
        // Starting these thread using start() method
        t1.start();
        t2.start();
    }
}

Producción: 

C. Sincronización estática

En esto, el método sincronizado se declara como «estático» , lo que significa que el bloqueo o el monitor se aplica a la clase, no al objeto, de modo que solo un subproceso accederá a la clase a la vez.

Ejemplo  

Java

// Java Program Illustrate  Synchronized
// Using static synchronization
 
// Class 1
// Helper class
class PrintTest extends Thread {
 
    // Static synchronization locks the class PrintTest
    synchronized public static void printThread(int n)
    {
 
        for (int i = 1; i <= 10; i++) {
 
            // Print message when threads are executing
            System.out.println("Thread " + n
                               + " is working...");
 
            // Try block to check for exceptions
            try {
 
                // making thread to sleep for 0.6 seconds
                Thread.sleep(600);
            }
 
            // Catch block to handle the exceptions
            catch (Exception ex) {
 
                // Print message when exception occurs
                System.out.println(ex.toString());
            }
        }
 
        // Display message for better readability
        System.out.println("--------------------------");
 
        try {
            Thread.sleep(1000);
        }
 
        catch (Exception ex) {
            System.out.println(ex.toString());
        }
    }
}
 
// Class 2
// Helper class extending Thread class
class Thread1 extends Thread {
 
    // run() method for thread
    public void run()
    {
 
        // Passing the class not the object
        PrintTest.printThread(1);
    }
}
 
// Class 3
// Helper class extending Thread class
class Thread2 extends Thread {
 
    public void run()
    {
 
        // Passing the class not the object
        PrintTest.printThread(2);
    }
}
 
// Class 4
// Main class
class SynchroTest {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // No shared object
        // Creating objects of class 2 and 3 that
        // are extending to Thread class
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
 
        // Starting thread with help of start() method
        t1.start();
        t2.start();
    }
}

Producción: 

Publicación traducida automáticamente

Artículo escrito por raismily01 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 *