java - ¿Por qué no puede AtomicBoolean ser un reemplazo de Boolean?
api-design (7)
Oracle JDK Javadoc para AtomicBoolean declara:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicBoolean.html
Un valor booleano que puede actualizarse atómicamente. Consulte la especificación del paquete java.util.concurrent.atomic para obtener una descripción de las propiedades de las variables atómicas. Un AtomicBoolean se usa en aplicaciones tales como banderas atómicamente actualizadas y no se puede usar como reemplazo de un booleano.
Un colega y yo estábamos tratando de descubrir un caso de uso donde AtomicBoolean no puede ser un sustituto y lo único que podemos pensar es que hay métodos que tiene el objeto booleano que AtomicBoolean no tiene.
¿Es esa la única razón o había algo más en mente cuando se escribió eso?
Boolean es un objeto de valor inmutable. Fue diseñado para ser inmutable y finalizado para hacer cumplir eso. java.lang.Boolean ha estado presente desde 1.0.
AtomicBoolean es mutable y está diseñado para actualizarse para que el valor actualizado sea visible en todos los hilos. AtomicBoolean fue presentado con Java 5.
Estos son conceptos completamente diferentes, por lo que AtomicBoolean no fue diseñado para extender Boolean. No puede sustituir un objeto mutable por uno inmutable sin destruir las invariantes esperadas del código que lo usa. El código que espera recibir un valor inmutable podría romperse si la versión atómica se pudiera pasar en su lugar.
Así que aquí hay un caso de uso: si se introdujo AtomicBoolean como algo que era sustituible por booleano, podría haber un caso en el que una clase creada antes de este cambio podría razonablemente esperar que en algún método que devuelva un booleano no tenga que pasar una defensa copiar a cuenta de que Boolean es inmutable. Si la referencia devuelta pasa a ser inicializada desde una fuente que cambia para usar AtomicBoolean en lugar de Boolean, entonces ese campo ahora podría ser modificado por cosas que llamen al método que devuelve Boolean, lanzándolo a AtomicBoolean.
Las clases atómicas están diseñadas para trabajar con actualizaciones simultáneas (como una mejora de la volatile
), pero la forma más eficiente de diseñar código simultáneo es usar valores inmutables. Por lo tanto, tenga cuidado de no confundir AtomicBoolean con "el Booleano que usa cuando escribe código multiproceso".
Ejemplo:
void doSomething( final Boolean flag ) {
final boolean before = flag.booleanValue();
do0( flag );
final boolean after = flag.booleanValue();
assert before == after;
if ( flag.booleanValue() ) {
do1();
}
if ( flag.booleanValue() ) {
do2();
}
}
puede dar un resultado diferente que
void doSomething( final AtomicBoolean flag ) {
final boolean before = flag.get();
do0( flag );
final boolean after = flag.get();
assert (before == after) || (before != after);
if ( flag.get() ) {
do1();
}
if ( flag.get() ) {
do2();
}
}
porque un AtomicBoolean
puede cambiar su valor mientras que un Boolean
no puede.
En el primer caso, do1()
y do2()
son ambos llamados o ninguno de ellos.
En el segundo caso, ambos, cualquiera, o ninguno de ellos puede ser llamado si el valor de AtomicBoolean
se modifica al mismo tiempo.
Como Boolean
siempre ha estado allí, y siempre se definió como inmutable, AtomicBoolean
, que se introdujo mucho más tarde, no puede ser sustituible por Boolean
porque se comporta de manera diferente y el código que se basa legítimamente en la inmutabilidad de un Boolean
puede romperse si se destruye esa inmutabilidad.
Tenga en cuenta que Boolean
no se puede sustituir por AtomicBoolean
y viceversa . Simplemente no son compatibles en su semántica.
El primer caso de uso principal de las operaciones atómicas (a veces encapsuladas) en cualquier idioma es la semántica de comparación y cambio, uno de los pilares fundamentales de las aplicaciones simultáneas.
El segundo uso principal es ocultar la complejidad de colocar correctamente cercas de memoria, necesarias para la semántica del modelo de memoria.
En Java Atomic * se encapsulan los dos anteriores, el primero con el código nativo específico de la plataforma y el último con la ayuda de la palabra clave volatile
.
No son auto-encapsulables, por lo que no se pueden usar en condicionales, por ejemplo,
// Explodey
if (someAtomicBoolean) {
}
Porque Boolean
es inmutable. Ver: ¿Por qué la clase Wrapper como Boolean en Java es inmutable? y mi respuesta:
Porque 2
es 2. No serán 3
mañana.
Inmutable es siempre preferido por defecto, especialmente en situaciones multiproceso, y hace que sea más fácil de leer y un código más fácil de mantener. Caso en cuestión: la API de Java Date
, que está plagada de fallas de diseño. Si Date
fuera inmutable, la API estaría muy optimizada. Yo sabría que las operaciones de Date
crearían nuevas fechas y nunca tendrían que buscar API que las modifiquen.
Lea Concurrencia en la práctica para comprender la verdadera importancia de los tipos inmutables.
Pero también tenga en cuenta que si por algún motivo desea tipos mutables, utilice AtomicInteger
AtomicBoolean
, etc. ¿Por qué Atomic
? Porque al introducir la mutabilidad, introdujo una necesidad de seguridad de hilos. Lo que no habría necesitado si sus tipos permanecieran inmutables, por lo que al usar tipos mutables también debe pagar el precio de pensar en la seguridad de hilos y usar tipos del paquete concurrent
. Bienvenido al maravilloso mundo de la programación concurrente.
Además, para Boolean
: lo desafío a nombrar una sola operación que desee realizar y que le interese si Boolean es mutable. establecido en verdad? Use myBool = true
. Esa es una reasignación, no una mutación. ¿Negar? myBool = !myBool
. La misma regla. Tenga en cuenta que la inmutabilidad es una característica , no una restricción, por lo que si puede ofrecerla, debe hacerlo, y en estos casos, por supuesto que puede hacerlo.
Tenga en cuenta que esto también se aplica a otros tipos. Lo más sutil con números enteros es count++
, pero eso es solo count = count + 1
, a menos que te importe obtener el valor atómicamente ... en cuyo caso usa el AtomicInteger
mutable.
Un caso de uso es que AtomicBoolean
no extiende Boolean
. Por lo tanto, si tiene un método como:
void foo (Boolean b) {
doStuff();
}
Entonces no puedes pasar un AtomicBoolean
como un parámetro para foo
. ( AtomicBoolean
llamar al método get()
del AtomicBoolean
).
Boolean
es la clase contenedora alrededor del boolean
primitivo. Puede ser creado automáticamente a partir de un boolean
por el compilador (conversión de boxeo) o convertido a booleano (conversión de unboxing). Este no es el caso de AtomicBoolean
donde es una clase separada diseñada para propósitos de concurrencia.
Por lo tanto, las dos clases tienen una semántica diferente en el nivel del idioma:
Boolean b = new Boolean(true);
AtomicBoolean ab = new AtomicBoolean(true);
System.out.println(true == b); // automatic unboxing of Boolean variable
System.out.println(true == ab); // compiler error