¿Por qué i++ no es atómico?

Resuelto Andie2302 asked hace 10 años • 11 respuestas

¿ Por qué no es i++atómico en Java?

Para profundizar un poco más en Java, intenté contar con qué frecuencia se ejecuta el bucle en los subprocesos.

Así que usé un

private static int total = 0;

en la clase principal.

Tengo dos hilos.

  • Hilo 1: ImpresionesSystem.out.println("Hello from Thread 1!");
  • Hilo 2: ImpresionesSystem.out.println("Hello from Thread 2!");

Y cuento las líneas impresas por el hilo 1 y el hilo 2. Pero las líneas del hilo 1 + las líneas del hilo 2 no coinciden con el número total de líneas impresas.

Aquí está mi código:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {

    private static int total = 0;
    private static int countT1 = 0;
    private static int countT2 = 0;
    private boolean run = true;

    public Test() {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        newCachedThreadPool.execute(t1);
        newCachedThreadPool.execute(t2);
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        run = false;
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println((countT1 + countT2 + " == " + total));
    }

    private Runnable t1 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT1++;
                System.out.println("Hello #" + countT1 + " from Thread 1! Total hello: " + total);
            }
        }
    };

    private Runnable t2 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT2++;
                System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    public static void main(String[] args) {
        new Test();
    }
}
Andie2302 avatar Aug 07 '14 01:08 Andie2302
Aceptado

i++Probablemente no sea atómico en Java porque la atomicidad es un requisito especial que no está presente en la mayoría de los usos de i++. Ese requisito tiene una sobrecarga significativa: hay un gran costo al hacer que una operación incremental sea atómica; Implica sincronización tanto a nivel de software como de hardware que no necesita estar presente en un incremento ordinario.

Se podría argumentar que i++debería haber sido diseñado y documentado para realizar específicamente un incremento atómico, de modo que se realice un incremento no atómico usando i = i + 1. Sin embargo, esto rompería la "compatibilidad cultural" entre Java, C y C++. Además, eliminaría una notación conveniente que los programadores familiarizados con lenguajes tipo C dan por sentada, dándole un significado especial que se aplica sólo en circunstancias limitadas.

El código básico C o C++ for (i = 0; i < LIMIT; i++)se traduciría a Java como for (i = 0; i < LIMIT; i = i + 1); porque sería inapropiado utilizar el atómico i++. Lo que es peor, los programadores que vienen de C u otros lenguajes similares a C y usan Java i++de todos modos, lo que resulta en un uso innecesario de instrucciones atómicas.

Incluso en el nivel del conjunto de instrucciones de la máquina, una operación de tipo incremental no suele ser atómica por motivos de rendimiento. En x86, se debe utilizar una instrucción especial "prefijo de bloqueo" para hacer que la incinstrucción sea atómica: por las mismas razones que antes. Si incsiempre fuera atómico, nunca se usaría cuando se requiera un inc no atómico; Los programadores y compiladores generarían código que carga, agrega 1 y almacena, porque sería mucho más rápido.

En algunas arquitecturas de conjuntos de instrucciones, no hay ninguna atómica inco quizás ninguna inc; Para hacer un inc atómico en MIPS, debe escribir un bucle de software que use and ll: scvinculado a carga y condicional de almacenamiento. El vinculado a la carga lee la palabra y el condicional de almacenamiento almacena el nuevo valor si la palabra no ha cambiado o falla (lo que se detecta y provoca un nuevo intento).

Kaz avatar Aug 06 '2014 22:08 Kaz

i++implica dos operaciones:

  1. leer el valor actual dei
  2. incrementar el valor y asignarlo ai

Cuando dos subprocesos actúan i++en la misma variable al mismo tiempo, ambos pueden obtener el mismo valor actual de iy luego incrementarlo y establecerlo en i+1, por lo que obtendrá un único incremento en lugar de dos.

Ejemplo :

int i = 5;
Thread 1 : i++;
           // reads value 5
Thread 2 : i++;
           // reads value 5
Thread 1 : // increments i to 6
Thread 2 : // increments i to 6
           // i == 6 instead of 7
Eran avatar Aug 06 '2014 18:08 Eran