Hable sobre cómo lograr rápidamente la seguridad de subprocesos en múltiples escenarios

Este artículo es compartido por la comunidad HUAWEI CLOUD " ¿Cómo lograr la seguridad de subprocesos en escenarios de subprocesos múltiples con solo 5 trucos? ", Autor: Java Campanilla.

1. Introducción

En la actualidad, con el rápido desarrollo del hardware informático, la CPU de una computadora personal también es multinúcleo, y ahora el número común de núcleos de CPU es de 4 u 8 núcleos. Por lo tanto, al escribir programas, para mejorar la eficiencia y aprovechar al máximo las capacidades del hardware, es necesario escribir programas paralelos. Como lenguaje principal de las aplicaciones de Internet, el lenguaje Java se usa ampliamente en el desarrollo de aplicaciones empresariales. También es compatible con subprocesos múltiples ( Multithreading ), pero aunque el subproceso múltiple es bueno, tiene requisitos más altos para la escritura de programas.

Un programa que puede ejecutarse correctamente con un solo subproceso no significa que pueda ejecutarse correctamente en un escenario de subprocesos múltiples. La corrección aquí a menudo no es fácil de encontrar, y solo aparecerá cuando el número de concurrencia alcance una cierta cantidad. Esta es también la razón por la que no es fácil de reproducir en la sesión de prueba. Por lo tanto, en un escenario de subprocesos múltiples (concurrencia), cómo escribir un programa seguro para subprocesos ( Thread-Safety ) es de gran importancia para el funcionamiento correcto y estable del programa. Lo siguiente combinará ejemplos para hablar sobre cómo implementar programas seguros para subprocesos en el lenguaje Java.

Para dar una comprensión perceptual, a continuación se proporciona un ejemplo de inseguridad de subprocesos:

package com.example.learn;
public class Counter {
    private static int counter = 0;
    public static int getCount(){
        return counter;
    }
    public static  void add(){
        counter = counter + 1;
    }
}1.2.3.4.5.6.7.8.9.10.

  
 

Esta clase tiene un contador de propiedades estáticas para contar. Entre ellos, el método estático add() se puede usar para agregar 1 al contador, o el valor actual del contador se puede obtener a través del método getCount(). Si se trata de un caso de subproceso único, este programa no es un problema, como repetir 10 veces, entonces el valor del contador de conteo final obtenido es 10. Sin embargo, en el caso de subprocesos múltiples, este resultado puede no obtenerse correctamente, puede ser igual a 10 o puede ser inferior a 10, como 9. A continuación se muestra un ejemplo de una prueba multiproceso:

package com.example.learn;
public class MyThread extends Thread{
    private String name ;
    public MyThread(String name){
        this.name = name ;
    }
    public void run(){
        Counter.add();
        System.out.println("Thead["+this.name+"] Count is "+  Counter.getCount());
    }
}
///
package com.example.learn;
public class Test01 {
    public static void main(String[] args) {
        for(int i=0;i<5000;i++){
            MyThread mt1 = new MyThread("TCount"+i);
            mt1.start();
        }
    }
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

  
 

Aquí, para reproducir el problema de conteo, la cantidad de subprocesos se ajusta para que sea relativamente grande, aquí es 5000. Ejecutando este ejemplo, la salida podría verse así:

Thead[TCount5] Count is 4
Thead[TCount2] Count is 9
Thead[TCount4] Count is 4
Thead[TCount14] Count is 10
..................................
Thead[TCount4911] Count is 4997
Thead[TCount4835] Count is 4998
Thead[TCount4962] Count is 49991.2.3.4.5.6.7.8.

  
 

Nota: En un escenario de subprocesos múltiples, el resultado de salida de un programa no seguro para subprocesos es incierto.

2, sincronizado 方法

Según el ejemplo anterior, la forma más directa de convertirlo en un programa seguro para subprocesos es agregar la  palabra clave sincronizada al método correspondiente para convertirlo en un método sincronizado . Puede decorar una clase, un método y un bloque de código. Modifique el programa de conteo anterior, el código es el siguiente:

package com.example.learn;
public class Counter {
    private static int counter = 0;
    public static int getCount(){
        return counter;
    }
    public static synchronized void add(){
        counter = counter + 1;
    }
}1.2.3.4.5.6.7.8.9.10.

  
 

Ejecutando el programa nuevamente, el resultado es el siguiente:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

  
 

3. Mecanismo de bloqueo

Otro método común de sincronización es el bloqueo. Por ejemplo, hay un bloqueo reentrante  ReentrantLock en Java , que es un mecanismo de sincronización recursivo y sin bloqueo . En comparación con  sincronizado  , puede proporcionar un mecanismo de bloqueo más potente y flexible. Puede reducir la probabilidad de ocurrencia de interbloqueo. El código de ejemplo es el siguiente:

package com.example.learn;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
    private  static int counter = 0;
    private static final ReentrantLock lock = new ReentrantLock(true);
    public static int getCount(){
        return counter;
    }
    public static  void add(){
        lock.lock();
        try {
            counter = counter + 1;
        } finally {
            lock.unlock();
        }
    }
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

  
 

Ejecutando el programa nuevamente, el resultado es el siguiente:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

  
 

Nota: Java también proporciona un bloqueo de lectura y escritura ReentrantReadWriteLock, que permite la separación de lectura y escritura y es más eficiente.

4. Uso de objetos atómicos

Dado que el mecanismo de bloqueo afectará un cierto rendimiento, en algunos escenarios, se puede implementar sin bloqueo. Java tiene clases de operaciones atómicas relacionadas con Atomic incorporadas, como AtomicInteger, AtomicLong, AtomicBoolean y AtomicReference, que se pueden seleccionar de acuerdo con diferentes escenarios. El código de ejemplo se proporciona a continuación:

package com.example.learn;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
    private static final AtomicInteger counter = new AtomicInteger();
    public static int getCount(){
        return counter.get();
    }
    public static void add(){
        counter.incrementAndGet();
    }
}1.2.3.4.5.6.7.8.9.10.11.

  
 

Ejecutando el programa nuevamente, el resultado es el siguiente:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

  
 

5. Objetos apátridas

Como se mencionó anteriormente, una de las razones de la inseguridad de los subprocesos es que varios subprocesos acceden a los datos en un objeto al mismo tiempo y los datos se comparten. Por lo tanto, si los datos se hacen exclusivos, es decir, sin estado , entonces, naturalmente, es un subproceso a salvo. El llamado método sin estado es dar la misma entrada y devolver el mismo resultado. El código de ejemplo se proporciona a continuación:

package com.example.learn;
public class Counter {
    public static int sum (int n) {
        int ret = 0;
        for (int i = 1; i <= n; i++) {
            ret += i;
        }
        return ret;
    }
}1.2.3.4.5.6.7.8.9.10.

  
 

6. Objetos inmutables

Como se mencionó anteriormente, si es necesario compartir un dato entre varios subprocesos y el valor dado de estos datos no se puede cambiar, también es seguro para subprocesos, lo que equivale a una propiedad de solo lectura. En Java, las propiedades se pueden modificar a través de la  palabra clave final  . El código de ejemplo se proporciona a continuación:

package com.example.learn;
public class Counter {
    public final int count ;
    public Counter (int n) {
        count = n;
    }
}1.2.3.4.5.6.7.

  
 

7. Resumen

Anteriormente se han mencionado varios métodos seguros para subprocesos. La idea general es lograr la sincronización a través de un mecanismo de bloqueo o evitar el intercambio de datos y evitar que los datos se lean y escriban en múltiples subprocesos. Además, algunos artículos dicen que la  modificación volátil  se puede usar frente a las variables para lograr el mecanismo de sincronización, pero esto no necesariamente se prueba después de la prueba. En algunos escenarios, volátil  aún no puede garantizar la seguridad de subprocesos. Aunque lo anterior es un resumen de la experiencia en seguridad de subprocesos, aún debe verificarse mediante pruebas rigurosas, y la práctica es el único criterio para probar la verdad.

Haga clic en Seguir para conocer las nuevas tecnologías de HUAWEI CLOUD por primera vez ~

Supongo que te gusta

Origin blog.csdn.net/devcloud/article/details/123889582
Recomendado
Clasificación