AtomicInteger para algoritmos sin bloqueo en Java

Los algoritmos sin bloqueo son uno de los mecanismos en los que es posible el acceso seguro para subprocesos a los datos compartidos sin el uso de primitivas de sincronización como mutexes. Las aplicaciones de subprocesos múltiples tienen recursos compartidos que se pueden pasar entre diferentes subprocesos utilizados en la aplicación. 

  • Esto plantea la amenaza de condiciones de carrera y carreras de datos entre los subprocesos. Para manejar esta situación, se utilizan varias técnicas. Una de las formas más comunes es usar sincronización y bloqueos (también llamados monitores) que garantizan la seguridad de los subprocesos. Sin embargo, si se utiliza una sincronización excesiva, el rendimiento de la aplicación se vería enormemente afectado y la aplicación se volvería cada vez más compleja.
     
  • Necesitamos escribir aplicaciones que sean seguras para subprocesos pero, al mismo tiempo, deberían brindarnos un alto rendimiento y el beneficio de la ejecución concurrente. Por lo tanto, para minimizar el uso de la sincronización y reducir la complejidad de los bloqueos, Java tiene un conjunto completo de clases en el paquete concurrente java.util.the que proporciona muchas operaciones avanzadas sin bloqueos y atómicas.
     
  • Se puede realizar un gran conjunto de operaciones en una aplicación sin necesidad de sincronizar la mayor parte del código. Necesitamos entender qué se aplica mejor en una situación y usar la herramienta adecuada para el trabajo.
     
  • Veremos un ejemplo en el que primero mostramos cómo se usa el bloqueo sincronizado en aplicaciones de subprocesos múltiples para lograr la concurrencia, y luego veremos una solución que proporciona una solución sin bloqueo del mismo problema.

El siguiente ejemplo muestra cómo se utilizan la sincronización y los mecanismos de bloqueo como solución para la concurrencia.
 

Java

// Java Program to demonstrate the 
// Synchronization of threads
// using Locks 
  
import java.io.*;
  
class GFG {
    public static void main(String[] args)
        throws InterruptedException
    {
        // Creating Obj for CountTrees Class 
        CountTrees countTrees = new CountTrees();
          
        // Creating Obj for IncreaseTrees Class 
        IncreaseTrees increaseTreesThread = new IncreaseTrees(countTrees);
          
        // Creating Obj for IncreaseConcrete Class
        IncreaseConcrete increaseConcreteThread
            = new IncreaseConcrete(countTrees);
          
        // Starting both Thread increaseTreesThread 
        // And increaseConcreteThread by using start method. 
        increaseTreesThread.start();
        increaseConcreteThread.start();
          
        // Join method Enable threads wait to complete 
        increaseTreesThread.join();
        increaseConcreteThread.join();
  
        // To print the no. of trees by getting current 
        // value by using countTrees Obj. 
        System.out.println("Number of trees in your area ::"
                           + countTrees.getNumOfTrees());
    }
}
  
// Implementation of IncreaseTrees using Thread
class IncreaseTrees extends Thread {
  
    private CountTrees countTrees;
  
    IncreaseTrees(CountTrees countTrees)
    {
        this.countTrees = countTrees;
    }
  
    @Override
    public void run()
    {
        System.out.println("Planting trees in progress...");
        countTrees.plantTrees();
    }
}
  
// Implementation of IncreaseConcrete using Thread
class IncreaseConcrete extends Thread {
  
    private CountTrees countTrees;
  
    IncreaseConcrete(CountTrees countTrees)
    {
        this.countTrees = countTrees;
    }
  
    @Override
    public void run()
    {
        System.out.println("Concretization in progress...");
        countTrees.concretizing();
    }
}
  
// Synchronizing the shared resources
class CountTrees {
  
    private int trees = 10000;
  
    public synchronized void plantTrees()
    {
        for (int i = 0; i < 10000; i++)
            trees++;
    }
  
    public synchronized void concretizing()
    {
        for (int i = 0; i < 10000; i++)
            trees--;
    }
  
    public synchronized int getNumOfTrees()
    {
        return this.trees;
    }
}

Producción:

Planting trees in progress...
Concretization in progress...
Number of trees in your area:: 10000

Ahora, para convertir el ejemplo anterior en una muestra simple sin bloqueo, usamos la clase AtomicInteger de Java, que forma parte del paquete Concurrent java.util.concurrent.atomic.AtomicInteger . Es una clase muy útil que se puede usar fácilmente en aplicaciones simultáneas como se muestra a continuación: 

Java

// Java program to demonstrate to achieve concurrency
// with the help of AtomicInteger class which is used
// in application like atomically incremented the counter
  
// Lock Free program for achieving concurrency
import java.io.*;
  
// Java provides wrapper class to achieve atomic 
// operations without the use of synchronization 
// like java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
  
class GFG {
  
    public static void main(String[] args)
        throws InterruptedException
    {
        // Creating Obj for CountTrees Class
        CountTrees countTrees = new CountTrees();
  
        // Creating Obj for IncreaseTrees Class
        IncreaseTrees increaseTreesThread
            = new IncreaseTrees(countTrees);
  
        // Creating Obj for IncreaseConcrete Class
        IncreaseConcrete increaseConcreteThread
            = new IncreaseConcrete(countTrees);
  
        // Starting both Thread increaseTreesThread
        // and increaseConcreteThread by using 
        // start method.
        increaseTreesThread.start();
        increaseConcreteThread.start();
  
        // join method Enable threads wait to complete
        increaseTreesThread.join();
        increaseConcreteThread.join();
  
        // To print the no. of trees by getting current
        // value by using countTrees Obj.
        System.out.println("Number of trees in your area ::"
                           + countTrees.getNumOfTrees());
    }
}
  
// Implementation of IncreaseTrees class
class IncreaseTrees extends Thread {
  
    private CountTrees countTrees;
  
    IncreaseTrees(CountTrees countTrees)
    {
        this.countTrees = countTrees;
    }
  
    @Override public void run()
    {
        System.out.println("Planting trees in process...");
        countTrees.plantTrees();
    }
}
  
class IncreaseConcrete extends Thread {
  
    private CountTrees countTrees;
  
    IncreaseConcrete(CountTrees countTrees)
    {
        this.countTrees = countTrees;
    }
  
    @Override public void run()
    {
        System.out.println("Concretization in progress...");
        countTrees.concretizing();
    }
}
  
// Implementation of CountTrees Class
class CountTrees {
  
    // In java AtomicInteger Class provides operations on
    // underlying int value that can be read and 
    // written atomically.
    private AtomicInteger trees = new AtomicInteger(10000);
  
    // Implementation of plantTrees method
    public void plantTrees()
    {
        // AtomicInteger class obj trees
        // atomically incremented the value.
        for (int i = 0; i < 10000; i++)
            trees.incrementAndGet();
    }
    // Implementation of concretizing method
    public void concretizing()
    {
        // AtomicInteger class obj trees
        // decremented the value.
        for (int i = 0; i < 10000; i++)
            trees.decrementAndGet();
    }
  
    public int getNumOfTrees()
    {
        // AtomicInteger class obj
        // trees Gets the current
        // value.
        return trees.get();
    }
}

Producción: 

Concretization in progress...
Planting trees in process...
Number of trees in your area::10000

Resumen y conclusiones clave:

  • La clase AtomicInteger es una gran herramienta que se puede usar en aplicaciones simples como el conteo simultáneo y la creación de código legible simple sin la complejidad de usar un candado.
  • AtomicInteger debe usarse solo cuando se necesitan operaciones atómicas. Además, la condición de carrera aún puede existir entre dos operaciones atómicas separadas.
  • La clase AtomicInteger está a la par y, a veces, puede ser más eficiente que un número entero regular con un candado como protección.
  • Si una aplicación solo usa un único subproceso, se prefiere el número entero regular.

Referencias: clase AtomicInteger en Java

Publicación traducida automáticamente

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