una tipos que programacion primitivos ejemplos declarar datos constante java multithreading

tipos - ¿Las primitivas Java son atómicas por diseño o por accidente?



tipos de variables en programacion (8)

¿Son enteros primitivos java (int) atómicos para nada? Algunos experimentos con dos hilos que comparten un int parecen indicar que sí lo son , pero, por supuesto, la ausencia de pruebas de que no lo son no implica que lo sean.

Específicamente, la prueba que ejecuté fue esta:

public class IntSafeChecker { static int thing; static boolean keepWatching = true; // Watcher just looks for monotonically increasing values static class Watcher extends Thread { public void run() { boolean hasBefore = false; int thingBefore = 0; while( keepWatching ) { // observe the shared int int thingNow = thing; // fake the 1st value to keep test happy if( hasBefore == false ) { thingBefore = thingNow; hasBefore = true; } // check for decreases (due to partially written values) if( thingNow < thingBefore ) { System.err.println("MAJOR TROUBLE!"); } thingBefore = thingNow; } } } // Modifier just counts the shared int up to 1 billion static class Modifier extends Thread { public void run() { int what = 0; for(int i = 0; i < 1000000000; ++i) { what += 1; thing = what; } // kill the watcher when done keepWatching = false; } } public static void main(String[] args) { Modifier m = new Modifier(); Watcher w = new Watcher(); m.start(); w.start(); } }

(y eso solo fue probado con java jre 1.6.0_07 en una PC con Windows de 32 bits)

Básicamente, el Modificador escribe una secuencia de conteo en el entero compartido, mientras que el Observador verifica que los valores observados nunca disminuyan. En una máquina donde se tuvo que acceder a un valor de 32 bits como cuatro bytes separados (o incluso dos palabras de 16 bits), habría una probabilidad de que el Vigilante atrape el entero compartido en un estado inconsistente y medio actualizado, y detecte el valor decreciente en lugar de aumentar Esto debería funcionar ya sea que los bytes de datos (hipotéticos) se recopilen o escriban en LSB 1st o MSB 1st, pero solo es probable en el mejor de los casos.

Parecería muy probable, dadas las amplias rutas de datos actuales, que un valor de 32 bits podría ser efectivamente atómico, incluso si la especificación Java no lo requiere. De hecho, con un bus de datos de 32 bits parecería que tendrías que trabajar más para obtener acceso atómico a bytes que a 32 bits.

Buscar en Google en "seguridad de subprocesos primitivos de Java" genera montones de cosas en clases y objetos seguros para subprocesos, pero al buscar la información en las primitivas parece estar buscando la proverbial aguja en un pajar.


Creo que no funciona como esperabas:

private static int i = 0; public void increment() { synchronized (i) { i++; } }

el entero es inmutable, por lo que está sincronizando en un objeto diferente todo el tiempo. int "i" está autoboxado a un objeto Integer, luego establece el bloqueo en él. Si otro subproceso entra en este método int i se autoelemba a otro objeto Integer y se establece el bloqueo en un objeto diferente que antes.


Esto es algo complicado y está relacionado con el tamaño de palabras del sistema. Bruce Eckel lo analiza con más detalle: Java Threads .


Esto no es atómico

i++;

Sin embargo, esto es:

i = 5;

Creo que aquí es donde se establece cierta confusión.


Estoy de acuerdo con Jon Skeet y me gustaría añadir que muchas personas confunden el concepto de atomicidad, volatilidad y seguridad de subprocesos porque a veces los términos se usan indistintamente.
Por ejemplo, considera esto:

private static int i = 0; public void increment() { i++; }

Si bien alguien puede argumentar que esta operación es atómica, la hipótesis referida es incorrecta.
La declaración i++; realiza tres operaciones:
1) Leer
2) Actualización
3) Escribir

Por lo tanto, los hilos que operan en esta variable deben sincronizarse así:

private static int i = 0; private static final Object LOCK = new Object(); public void increment() { synchronized(LOCK) { i++; } }

o esto:

private static int i = 0; public static synchronized void increment() { i++; }

Tenga en cuenta que, para una única instancia de objeto, llamar a un método al que acceden múltiples hilos y operar en datos mutables compartidos, se debe tener en cuenta que los parámetros de un método, la variable local y el valor de retorno son locales para cada subproceso.

Para obtener más información, consulte este enlace:
http://www.javamex.com/tutorials/synchronization_volatile.shtml

Espero que esto ayude.

ACTUALIZACIÓN : También existe el caso donde puede sincronizar en el objeto de la clase en sí. Más información aquí: ¿Cómo sincronizar una variable estática entre hilos que ejecutan diferentes instancias de una clase en Java?


Las lecturas atómicas y las escrituras significan simplemente que nunca leerá, por ejemplo, los primeros 16 bits de una actualización int y otra de un valor anterior.

Esto no dice nada sobre CUÁNDO otros hilos ven estas escrituras.

La larga historia corta es que cuando dos subprocesos corren sin barreras de memoria entre ellos algo se pierde.

Haga girar dos o más subprocesos que incrementen un único entero compartido y también cuenten sus propios incrementos. Cuando el entero llega a algún valor (INT_MAX, por ejemplo, agradable y grande para permitir que las cosas se calienten) lo detiene todo y devuelve el valor del int y el número de incrementos que realizó cada subproceso.

import java.util.Stack; public class Test{ static int ctr = Integer.MIN_VALUE; final static int THREADS = 4; private static void runone(){ ctr = 0; Stack<Thread> threads = new Stack<>(); for(int i = 0; i < THREADS; i++){ Thread t = new Thread(new Runnable(){ long cycles = 0; @Override public void run(){ while(ctr != Integer.MAX_VALUE){ ctr++; cycles++; } System.out.println("Cycles: " + cycles + ", ctr: " + ctr); } }); t.start(); threads.push(t); } while(!threads.isEmpty()) try{ threads.pop().join(); }catch(InterruptedException e){ // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(); } public static void main(String args[]){ System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE)); System.out.println(" Int Max: " + Integer.MAX_VALUE); System.out.println(); for(;;) runone(); } }

Este es el resultado de esta prueba en mi caja de cuatro núcleos (siéntete libre de jugar con el recuento de subprocesos en el código, simplemente coincidí con mi conteo de núcleos, obviamente):

Int Range: 4294967295 Int Max: 2147483647 Cycles: 2145700893, ctr: 76261202 Cycles: 2147479716, ctr: 1825148133 Cycles: 2146138184, ctr: 1078605849 Cycles: 2147282173, ctr: 2147483647 Cycles: 2147421893, ctr: 127333260 Cycles: 2146759053, ctr: 220350845 Cycles: 2146742845, ctr: 450438551 Cycles: 2146537691, ctr: 2147483647 Cycles: 2110149932, ctr: 696604594 Cycles: 2146769437, ctr: 2147483647 Cycles: 2147095646, ctr: 2147483647 Cycles: 2147483647, ctr: 2147483647 Cycles: 2147483647, ctr: 330141890 Cycles: 2145029662, ctr: 2147483647 Cycles: 2143136845, ctr: 2147483647 Cycles: 2147007903, ctr: 2147483647 Cycles: 2147483647, ctr: 197621458 Cycles: 2076982910, ctr: 2147483647 Cycles: 2125642094, ctr: 2147483647 Cycles: 2125321197, ctr: 2147483647 Cycles: 2132759837, ctr: 330963474 Cycles: 2102475117, ctr: 2147483647 Cycles: 2147390638, ctr: 2147483647 Cycles: 2147483647, ctr: 2147483647


Todos los accesos de memoria en Java son atómicos por defecto, con la excepción de long y double (que pueden ser atómicos, pero no tienen que serlo). No es muy claro para ser honesto, pero creo que esa es la implicación.

De la sección 17.4.3 del JLS:

Dentro de una ejecución secuencialmente consistente, hay un orden total sobre todas las acciones individuales (como lecturas y escrituras) que es consistente con el orden del programa, y ​​cada acción individual es atómica y es inmediatamente visible para cada hilo.

y luego en 17.7 :

Algunas implementaciones pueden encontrar conveniente dividir una sola acción de escritura en un valor doble o largo de 64 bits en dos acciones de escritura en valores adyacentes de 32 bits. Por el bien de la eficiencia, este comportamiento es específico de la implementación; Las máquinas virtuales Java son libres de realizar escrituras en valores largos y dobles de forma atómica o en dos partes.

Sin embargo, tenga en cuenta que la atomicidad es muy diferente a la volatilidad.

Cuando un hilo actualiza un número entero a 5, se garantiza que otro hilo no verá 1 o 4 o cualquier otro estado intermedio, pero sin ninguna volatilidad o bloqueo explícito, el otro hilo podría ver 0 para siempre.

Con respecto a trabajar duro para obtener acceso atómico a bytes, tienes razón: la VM bien puede tener que esforzarse ... pero tiene que hacerlo. De la sección 17.6 de la especificación:

Algunos procesadores no proporcionan la capacidad de escribir en un solo byte. Sería ilegal implementar actualizaciones de arreglos de bytes en un procesador de este tipo simplemente leyendo una palabra completa, actualizando el byte apropiado y luego escribiendo la palabra completa en la memoria. Este problema a veces se conoce como desgarro de palabras, y en procesadores que no pueden actualizar fácilmente un solo byte de forma aislada, se requerirá algún otro enfoque.

En otras palabras, le corresponde a la JVM hacerlo bien.


Una lectura o escritura de un entero o cualquier otro tipo más pequeño debe ser atómico, pero como anotó Robert, los largos y los dobles pueden o no depender de la implementación. Sin embargo, cualquier operación que use tanto una lectura como una escritura, incluidos todos los operadores de incremento, no son atómicas. Por lo tanto, si tiene hilos que operan en un entero i = 0, uno tiene i ++ y el otro i = 10, el resultado podría ser 1, 10 o 11.

Para operaciones como esta, debe mirar java.sun.com/javase/6/docs/api/java/util/concurrent/atomic/… que tiene métodos para modificar atómicamente un valor mientras recupera el anterior o para incrementar atómicamente el valor.

Finalmente, los subprocesos pueden almacenar en caché el valor de la variable y no verán los cambios realizados en ella desde otros subprocesos. Para asegurarse de que ambos subprocesos siempre vean los cambios realizados por el otro subproceso, debe marcar la variable como volátil.


  • Ninguna cantidad de pruebas puede probar la seguridad del hilo, solo puede desaprobarlo ;
  • Encontré una referencia indirecta en JLS 17.7 que indica

Algunas implementaciones pueden encontrar conveniente dividir una sola acción de escritura en un valor doble o largo de 64 bits en dos acciones de escritura en valores adyacentes de 32 bits.

y más abajo

A los efectos del modelo de memoria del lenguaje de programación Java, una sola escritura en un valor largo o doble no volátil se trata como dos escrituras separadas: una para cada mitad de 32 bits.

Esto parece implicar que las escrituras a los enteros son atómicas.