miércoles, 25 de abril de 2012

Sobre variables estáticas en Java

La pasada semana leyendo el capítulo dedicado a la programación concurrente del expléndido libro de Bruce Ekell, Thinking in Java, me encontré con un ejemplo que me distrajo de mi lectura principal. En el ejemplo, Bruce pretendía ilustrar el uso de los Threads en Java usando una clase que implementaba una cuenta atrás.

package test.specifications;
    
public class LiftOff implements Runnable {
    
 protected int countDown = 10; // Default
 private static int taskCount = 0;
 private final int id = taskCount++;
    
 public LiftOff () {}
    
 public LiftOff(int countDown) {
  this.countDown = countDown;
 }
      
 public String status() {
  return "#" + id + "(" + (countDown > 0 ? countDown :
   "Liftoff!") + " ) , ";
 }
    
 public void run() {
  while (countDown-- > 0) {
   System.out.print( status() ) ;
   Thread.yield();
  }
 }
    
}
   

Bruce ilustraba el uso de la clase anterior con el código siguiente:

package test.specifications;

public class MoreBasicThreads {
   
    public static void main(String[] args){
        for(int i = 0; i < 5; i++)
            new Thread(new LiftOff()).start();
       
        System.out.println("Waiting for LiftOff");
    }

}
   

Atrajo mi atención el resultado obtenido al ejecutar el código anterior.


#0(9 ) , #4(9 ) , Waiting for LiftOff #3(9 ) , #2(9 ) , #1(9 ) , #4(8 ) , #1(8 ),
#4(7 ) , #3(8 ) , #4(6 ) , #0(8 ) , #4(5 ) , #3(7 ) , #1(7 ) , #2(8 ) , #1(6 ) , 
#3(6 ) , #4(4 ) , #0(7 ) , #4(3 ) , #3(5 ) , #1(5 ) , #2(7 ) , #1(4 ) , #3(4 ) , 
#4(2 ) , #0(6 ) , #4(1 ) , #3(3 ) , #1(3 ) , #2(6 ) , #1(2 ) , #3(2 ) , #3(1 ) ,
#4(Liftoff! ) , #0(5 ) , #0(4 ) , #0(3 ) , #0(2 ) , #0(1 ) , #0(Liftoff! ) ,
#3(Liftoff! ) , #1(1 ) , #2(5 ) , #1(Liftoff! ) , #2(4 ) , #2(3 ) , #2(2 ) , 
#2(1 ) , #2(Liftoff! ) , 
   

Lo que llamó mi atención de este resultado fue como el valor de la variable id, asignado a partir de la variable estática taskCount, cambiaba para las distintas instancias de la clase LiftOff. En un principio, confundido por la inicialización de la variable id (mediante asignación de la variable estática taskCount), esperaba que todos los valores de id fuesen iguales.

La evidencia del resultado y una mirada más en detalle me hicieron salir de mi error.

Para explicar este resultado, partimos de la definición de variables estáticas de la especificación de Java ( http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.1

If a field is declared  static , there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. A static field, sometimes called a class variable, is incarnated when the class is initialized

Efectivamente el valor de la variable taskCount es compartido por todas la instancias de la clase LiftOff, pero este valor se asigna a la variable final id y posteriormente se incrementa mediante el operador ++.
private final int id = taskCount++;

De esta forma la siguiente vez que se que se instancia la clase LiftOff, el valor de taskCount ha cambiado, con lo que a la variable id se le asigna el nuevo valor de la variable taskCount, valor distinto que al que se asigno a la variable id de la instancia anterior (de echo es el valor anterior incrementado en una unidad). Y así sucesivamente para las siguientes instancias.

Así que, aunque el resultado aparentemente parece contradecir la definición de variable estática mostrando valores distintos de taskCount para las distintas instancias de LiftOff, la realidad, descrita en los párrafos anteriores, es muy distinta y concordante con la especificación.