La clase Atomic en el paquete concurrente de java

La clase Atomic en el paquete concurrente de java

sky ao IT ja ja
este es un caso real, han provocado una gran controversia, la causa de la historia es muy simple, es la necesidad de implementar un contador simple, luego cada valor más 1, por lo que existe el siguiente código:


          private int counter = 0;
          public int getCount ( ) {
                   return counter++;
          }

Este contador se utiliza para generar un sessionId, que se utiliza para interactuar con el sistema de facturación externo. Este sessionId, por supuesto, debe ser globalmente único y no duplicado. Pero desafortunadamente, finalmente se encontró que el código anterior producía la misma identificación, por lo que causará algunos errores inexplicables en algunas solicitudes ... Más dolorosamente, el código anterior es una clase de herramienta desarrollada por otros departamentos. Simplemente tomé su paquete jar para llamarlo, no hay código fuente, y no pensé que hubiera un error tan terrible y de bajo nivel en él.

Debido a la repetición de sessionId, algunas solicitudes fallan, aunque la probabilidad de ocurrencia es extremadamente baja, no siempre es posible reproducir la prueba todos los días. Debido a que está relacionado con la facturación, incluso si la probabilidad de error es baja, debe resolverse. La situación real es que en la etapa final del desarrollo del proyecto, comenzó la prueba de estabilidad final antes del lanzamiento. En la prueba continua de 7 * 24 horas, este problema a menudo reaparecía solo unos días después del inicio de la prueba, y yo era responsable de la resolución de problemas. Mi colega se movía miserablemente ... Después de repetidas búsquedas, alguien finalmente sospechó que habían venido aquí, descompilaron el paquete del frasco y vieron el código anterior que no funcionaba.

Este error de bajo nivel proviene de un conocimiento básico de las
operaciones java: ++, ya sean i ++ o ++ i, ¡no son operaciones atómicas!

Y una operación no atómica, habrá problemas de seguridad de subprocesos bajo concurrencia de subprocesos múltiples: aquí hay una pequeña explicación, el operador "++" anterior, en principio, en realidad contiene lo siguiente: calcular el nuevo valor después de sumar 1, y luego Asigne este nuevo valor a la variable original y devuelva el valor original. Similar al código siguiente


          private int counter = 0;
          public int getCount ( ) {
                   int result = counter;
                   int newValue = counter + 1; // 1. 计算新值
                   counter = newValue;         // 2. 将新值赋值给原变量
                   return result;
          }

Cuando varios subprocesos son concurrentes, si dos subprocesos llaman al método getCount () al mismo tiempo, pueden obtener el mismo valor de contador. Para garantizar la seguridad, uno de los métodos más simples es sincronizar en el método getCount ():


          private int counter = 0;
          public synchronized int getCount ( ) {
                   return counter++;
          }

De esta forma, se puede evitar el peligro de concurrencia causado por la naturaleza no atómica del operador ++.
Extendamos un poco este caso: si la operación aquí es atómica, ¿puede ser posible el acceso concurrente sin sincronización y seguridad? Modificaremos ligeramente este código:


          private int something = 0;
          public int getSomething ( ) {
                   return something;
          }
          public void setSomething (int something) {
                   this.something = something;
          }

Suponiendo que hay varios subprocesos que acceden simultáneamente a los métodos getSomething () y setSomething (), cuando un subproceso establece un nuevo valor llamando al método setSomething (), ¿pueden otros métodos que llaman a getSomething () leer inmediatamente el nuevo valor? ? Aquí "this.something = something;" es una asignación al tipo int. De acuerdo con la especificación del lenguaje Java, la asignación a int es una operación atómica. No hay peligro oculto de la operación no atómica en el caso anterior.

Pero todavía hay un tema importante aquí, llamado "Visibilidad de la memoria". Aquí se incluye una serie de conocimientos sobre el modelo de memoria Java. Tiene un espacio limitado y no se describirá en detalle. Si no conoce estos puntos de conocimiento, puede consultar la información usted mismo. La forma más sencilla es buscar en Google estas dos palabras clave "modelo de memoria Java", "Java Visibilidad de la memoria ". O puede consultar esta publicación "Resumen de seguridad de subprocesos de Java",

Hay dos formas de resolver el problema de "visibilidad de la memoria" aquí, una es continuar usando la palabra clave sincronizada, el código es el siguiente


          private int something = 0;
          public synchronized  int getSomething ( ) {
                   return something;
          }
          public synchronized  void setSomething (int something) {
                   this.something = something;
          }
 另一个是使用volatile 关键字,

          private volatile int something = 0;
          public int getSomething ( ) {
                   return something;
          }
          public void setSomething (int something) {
                   this.something = something;
          }

La solución que utiliza la palabra clave volátil es mucho mejor en términos de rendimiento, porque volatile es una sincronización ligera que solo puede garantizar la visibilidad de la memoria multiproceso, pero no puede garantizar la ejecución ordenada de multiproceso. Por lo tanto, la sobrecarga es mucho menor que la sincronizada.

Volvamos al principio del caso, porque usamos el método modificado de agregar sincronizado directamente antes del método getCount (), evitando así no solo el orden de ejecución multiproceso causado por operaciones no atómicas, sino también "por cierto" Problemas de visibilidad de la memoria resueltos.

De acuerdo, ahora puede continuar.Como se mencionó anteriormente, el problema se puede resolver agregando sincronizado antes del método getCount (), pero en realidad hay una forma más conveniente de usar las clases atómicas provistas en el paquete concurrente introducido después de jdk 5.0. java.util.concurrent.atomic.Atomic ***, como AtomicInteger, AtomicLong, etc.


        private AtomicInteger  counter = new AtomicInteger(0);
        public int getCount ( ) {
             return counter.incrementAndGet();
        }

La clase Atomic no solo proporciona garantía de seguridad de subprocesos para operaciones de datos, sino que también proporciona una serie de métodos con semántica clara como incrementAndGet (), getAndIncrement, addAndGet (), getAndAdd (), que son fáciles de usar. Más importante aún, la clase Atomic no es un simple paquete de sincronización. Su implementación interna no es simplemente el uso de sincronizados, sino una forma más eficiente de CAS (comparar e intercambiar) + volátil, evitando así la alta sobrecarga de sincronización y eficiencia de ejecución. Muy mejorado. Debido a limitaciones de espacio, el principio de "CAS" no se discutirá aquí.

Por lo tanto, por razones de rendimiento, se recomienda encarecidamente utilizar la clase Atomic tanto como sea posible en lugar de escribir la implementación de código basada en la palabra clave sincronizada.
Finalmente, para resumir, en esta publicación hablamos de varios temas:
1. ++ las operaciones no son operaciones atómicas
2. Las operaciones no atómicas tienen problemas de seguridad de subprocesos
3. Visibilidad de la memoria bajo concurrencia
4. Las clases atómicas se pueden lograr a través de CAS + volatile Es más eficiente que sincronizado y recomendado.

Supongo que te gusta

Origin blog.51cto.com/15061944/2593719
Recomendado
Clasificación