Lugar para compartir información interesante con mis amigos.

Thursday, April 23, 2015

Tutorial de concurrencia en Java 7, Parte 2

Objetos Inmutables


Esta es una entrada resultado de la traducción el tutorial para Concurrencia publicado en la página oficial de Oracle.

Un objeto es considerado inmutable si su estado no puede ser cambiado después de ser creado. La máxima dependencia en objetos inmutables es ampliamente aceptada como una buena estrategia para crear código simple y confiable.


Programadores son a veces reacios a emplear objetos inmutables, porque ellos se preocupan acerca del costo de crear un objeto nuevo como oposición a actualizar un objeto in situ. El impacto de la creación es a veces sobreestimado, y puede ser compensado por algunas de las eficiencias asociadas a los objetos inmutables.Estos incluyen  la disminución de la sobrecarga debido al funcionamiento del Garbage Collector, y la eliminación del código necesario para proteger objetos mutables de la corrupción.




La siguientes subsecciones toman una clase cuyas instancias son mutables y derivan una clase con instancias inmutables de ella. Al hacerlo, se dan normas generales para este tipo de conversión y demuestran algunas de las ventajas de los objetos inmutables.

Ejemplo de una clase sincronizada



La clase, SinchronizedRGB, define objetos que representan colores. Cada objeto representa el color como tres enteros que representan los colores primarios y una cadena que da el nombre del color.
public class SynchronizedRGB {

   // Values must be between 0 and 255.
   private int red;
   private int green;
   private int blue;
   private String name;

   private void check(int red,
                      int green,
                      int blue) {
       if (red < 0 || red > 255
           || green < 0 || green > 255
           || blue < 0 || blue > 255) {
           throw new IllegalArgumentException();
       }
   }

   public SynchronizedRGB(int red,
                          int green,
                          int blue,
                          String name) {
       check(red, green, blue);
       this.red = red;
       this.green = green;
       this.blue = blue;
       this.name = name;
   }

   public void set(int red,
                   int green,
                   int blue,
                   String name) {
       check(red, green, blue);
       synchronized (this) {
           this.red = red;
           this.green = green;
           this.blue = blue;
           this.name = name;
       }
   }

   public synchronized int getRGB() {
       return ((red << 16) | (green << 8) | blue);
   }

   public synchronized String getName() {
       return name;
   }

   public synchronized void invert() {
       red = 255 - red;
       green = 255 - green;
       blue = 255 - blue;
       name = "Inverse of " + name;
   }
}


SynchronizedRGB debe ser usado con cuidado para evitar ser vista en un estado inconsistente. Suponga, por ejemplo, un hilo ejecuta el siguiente código:


SynchronizedRGB color =
   new SynchronizedRGB(0, 0, 0, "Pitch Black");
...
int myColorInt = color.getRGB();      //Statement 1
String myColorName = color.getName(); //Statement 2


Si otro hilo invoca color.set después del Statement 1 pero antes del Statement 2, el valor de myColorInt no va a coincidir con el valor de myColorName. Para evitar este resultado, las dos sentencias deben estar juntas:


synchronized (color) {
   int myColorInt = color.getRGB();
   String myColorName = color.getName();
}


Este tipo de inconsistencia es solo posible para objetos mutables -- este no va a ser un problema para la versión inmutable de SynchronizedRGB


Una estrategia para definir objetos inmutables



Las siguientes reglas definen una estrategia simple para crear objetos inmutables. No todas las clases documentadas como inmutables siguen estas reglas. Esto no necesariamente significa que los creadores de estas clases fueran descuidados -- ellos pudieron tener buenas razones para creer que instancias de sus clases nunca cambiarán después de su construcción. Sin embargo, estas estrategias requieren análisis sofisticados que no son para principiantes.
  1. No facilitar métodos “setter” -- métodos que modifican los campos o objetos referidos por atributos.
  2. Hacer todos los atributos final y private.
  3. No permitir a las subclases sobreescribir métodos. La manera fácil de hacer esto es declarar la clase como final. Un acercamiento más sofisticado es hacer el constructor privado y construir instancias en métodos fábrica.
  4. Si los atributos de instancia incluyen referencias a objetos mutables, no permitir el cambio de esos objetos.
    1. No facilitar métodos que modifiquen objetos mutables.
    2. No compartir referencias a los objetos mutables. Nunca almacenar referencias a objetos externos mutables pasados al constructor; si es necesario, crear copias, y almacenar referencias a las copias. De manera similar, crear copias de los objetos internos mutables cuando es necesario evitar el retorno de los originales en tus métodos.


Aplicar esta estrategia en SynchronizedRGB resulta en los siguientes pasos:
  1. Hay dos métodos setter en esta clase. El primero, set, transforma arbitrariamente el objeto, y no tiene lugar en una versión inmutable de la clase. El segundo, invert, puede ser adaptado creando un nuevo objeto en lugar de modificar el existente.
  2. Todos los atributos ya son private. Ellos son además calificados como final.
  3. La clase misma es declarada como final.
  4. Solo un atributo refiere a un objeto, y ese objeto mismo es inmutable. Por lo tanto no hay garantías de que cambiando el estado de objetos inmutables contenidos sea necesario.


Después de los cambios, tenemos ImmutableRGB:


final public class ImmutableRGB {

   // Values must be between 0 and 255.
   final private int red;
   final private int green;
   final private int blue;
   final private String name;

   private void check(int red,
                      int green,
                      int blue) {
       if (red < 0 || red > 255
           || green < 0 || green > 255
           || blue < 0 || blue > 255) {
           throw new IllegalArgumentException();
       }
   }

   public ImmutableRGB(int red,
                       int green,
                       int blue,
                       String name) {
       check(red, green, blue);
       this.red = red;
       this.green = green;
       this.blue = blue;
       this.name = name;
   }


   public int getRGB() {
       return ((red << 16) | (green << 8) | blue);
   }

   public String getName() {
       return name;
   }

   public ImmutableRGB invert() {
       return new ImmutableRGB(255 - red,
                      255 - green,
                      255 - blue,
                      "Inverse of " + name);
   }
}


Objetos de concurrencia de alto nivel

Hasta el momento, esta lección se ha enfocado en APIs de bajo nivel que han sido parte de la plataforma Java desde el comienzo. Estas APIs son adecuadas para tareas muy básicas, pero para tareas más avanzadas son necesarios bloques de más alto nivel. Esto es especialmente verdadero para aplicaciones masivamente concurrentes que explotan totalmente los sistemas actuales multiprocesador y multi-núcleo.


En esta sección vamos a ver algunas de las características de alto nivel introducidas con la versión 5.0 de Java. La mayoría de estas características son implementadas en los nuevos paquetes java.util.concurrent. Hay también nuevas estructuras de datos concurrentes en el Framework de Colecciones de Java.


  • Objetos Lock soportan mecanismos de bloqueo que simplifican muchas aplicaciones concurrentes.
  • Executors define un API de alto nivel para lanzar y administrar hilos. Implementaciones de Ejecutores contenida en java.util.concurrent ofrecen administración de grupo de hilos adecuado para aplicaciones de gran escala.
  • Colecciones concurrentes hacen mucho más fácil administrar grandes colecciones de datos y pueden reducir enormemente la necesidad de sincronización.
  • Variables Atómicas tienen características que minimizan sincronización y ayudan a evitar errores de consistencia de memoria.
  • ThreadLocalRandom (En JDK 7) ofrece una eficiente generación de números pseudoaleatorios desde múltiples hilos.


Objetos Lock

Código sincronizado se basa en una clase simple de ReentrantLock. Este tipo de lock es fácil de usar, pero tiene muchas limitaciones. Mecanismos más sofisticados de locking son soportados por el paquete java.util.concurrent.locks . Nosotros no examinaremos este paquete en detalle, pero en lugar vamos a enfocarnos en la interfaz más básica, Lock.
Objetos Lock trabajan muy parecido a lock implícitos usados por código sincronizado. Como con los locks implícitos, solo un hilo puede obtener un objeto Lock a la vez. objetos Lock también soportan el mecanismo wait/notify, pero a través de su objeto asociado Condition .


La más grande ventaja de los objetos Lock sobre locks implícitos es su habilidad para devolverse en un intento de adquirir un lock. El método tryLock se devuelve si el lock no está disponible inmediatamente o ante la expiración de un periodo de tiempo (si se especifica). El método lockInterruptibly  se devuelve si otro hilo envía una interrupción antes que el lock es obtenido.


Vamos a usar objetos Lock para resolver el problema del abrazo mortal que vimos antes. Alfonso y Gastón han entrenado ellos mismos para notar cuando un amigo va a hacer la inclinación. Nosotros modelamos esta mejora pidiendo que nuestro objeto Friend debe obtener el lock para ambos participantes antes de proceder con la inclinación. Aquí está el código para el modelo mejorado,  Safelock. Para demostrar la versatilidad de esta estrategia, vamos a asumir que alfonso y Gastón están muy encaprichados con su nueva habilidad para hacer la inclinación de forma segura que ellos no puedan parar de inclinarse uno al otro.


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Random;

public class Safelock {
   static class Friend {
       private final String name;
       private final Lock lock = new ReentrantLock();

       public Friend(String name) {
           this.name = name;
       }

       public String getName() {
           return this.name;
       }

       public boolean impendingBow(Friend bower) {
           Boolean myLock = false;
           Boolean yourLock = false;
           try {
               myLock = lock.tryLock();
               yourLock = bower.lock.tryLock();
           } finally {
               if (! (myLock && yourLock)) {
                   if (myLock) {
                       lock.unlock();
                   }
                   if (yourLock) {
                       bower.lock.unlock();
                   }
               }
           }
           return myLock && yourLock;
       }
           
       public void bow(Friend bower) {
           if (impendingBow(bower)) {
               try {
                   System.out.format("%s: %s has"
                       + " bowed to me!%n",
                       this.name, bower.getName());
                   bower.bowBack(this);
               } finally {
                   lock.unlock();
                   bower.lock.unlock();
               }
           } else {
               System.out.format("%s: %s started"
                   + " to bow to me, but saw that"
                   + " I was already bowing to"
                   + " him.%n",
                   this.name, bower.getName());
           }
       }

       public void bowBack(Friend bower) {
           System.out.format("%s: %s has" +
               " bowed back to me!%n",
               this.name, bower.getName());
       }
   }

   static class BowLoop implements Runnable {
       private Friend bower;
       private Friend bowee;

       public BowLoop(Friend bower, Friend bowee) {
           this.bower = bower;
           this.bowee = bowee;
       }
   
       public void run() {
           Random random = new Random();
           for (;;) {
               try {
                   Thread.sleep(random.nextInt(10));
               } catch (InterruptedException e) {}
               bowee.bow(bower);
           }
       }
   }
           

   public static void main(String[] args) {
       final Friend alphonse =
           new Friend("Alphonse");
       final Friend gaston =
           new Friend("Gaston");
       new Thread(new BowLoop(alphonse, gaston)).start();
       new Thread(new BowLoop(gaston, alphonse)).start();
   }
}


Ejecutores

En todos los ejemplos anteriores, hay una conexión cercana entre la tarea a ser realizada por el nuevo hilo, como se definió en el objeto Runnable, y el hilo mismo, como está definido por el objeto Thread. Este trabaja bien para aplicaciones pequeñas, pero en aplicaciones de gran escala, tiene sentido separar la administración y creación de hilos y  el resto de la aplicación. Objetos que encapsulan estas funciones son conocidos como ejecutores. Las siguientes subsecciones describen los ejecutores en detalle.


  • Interfaces Executor define tres tipos de objetos ejecutores.
  • Pools de Hilos son el tipo más común de implementación de ejecutores.
  • Fork/Join es un framework (nuevo en java 7) para tomar ventaja de múltiples procesadores.


Interfaces Executor

El paquete java.util.concurrent define tres interfaces executor.


  • Executor, una interface simple que soporta el lanzamiento de nuevas tareas.
  • ExecutorService, una subinterface de Executor, la cual agrega características que ayudan a administrar el ciclo de vida, tanto de tareas individuales como del ejecutor mismo.
  • SheduledExecutorServie, una subinterface de ExecutorService, que soporta la ejecución de tareas en el futuro y de forma periódica.


Típicamente, las variables que se refieren a objetos executor son declaradas  como uno de estos tres tipos, no con una clase tipo (Class) específica de ejecutor.


La interface Executor

La interface Executor ofrece un único método, execute, diseñado para ser una gota de reemplazo de la estrategia común de creación de hilos. Si r es un objeto Runnable, y e es un objeto Executor se puede reemplazar:


(new Thread(r)).start();
with
e.execute(r);


Sin embargo, la definición de execute es menos específica. La estrategia de bajo nivel crea un nuevo hilo y lo lanza inmediatamente, Dependiendo de la implementación del Excecutor, execute puede hacer la misma cosa, pero es más posible usar un hilo trabajador existente para correr r, o para reemplazar r en la cola para esperar por un hilo trabajador que esté disponible (Vamos a describir hilos trabajadores en la sección Thread Pools).


Las implementaciones de executor en java.util.concurrent están diseñadas para hacer completo uso de  las interfaces más avanzadas ExecutorService y ScheduledExecutorService, aunque ellas también trabajan con la interface base Executor.


La interface ExecutorService



La interface ExcecutorService complementa execute con un parecido, pero más versátil método submit. Así como execute, submit acepta objetos Runnable, pero también acepta objetos Callable, los cuales permiten que las tareas retornen valores. El método submit retorna un objeto Future, el cual es usado para recibir  el valor de retorno del Callable y para manejar el estado de ambas tareas Callable y Runnable.


ExecutorService también ofrece métodos para enviar grandes colecciones de objetos Callable. Finalmente, ExecutorService ofrece un número de métodos para administrar el apagado del ejecutor. Para soportar apagado inmediato, las tareas deberían manejar las interrupciones correctamente.


Interface ScheduledExecutorService



La interface  ScheduledExecutorService  complementa los metodos de su interface padre ExecutorService con shedule, el cual ejecuta una tarea Runnable o Callable después de un retraso especificado. Además, la interface define los métodos scheduleAtFixedRate y scheduleWithFixedDelay, los cuales ejecutan tareas especificadas repetidamente a intervalos definidos.


Pool de Hilos



La mayoría de implementaciones de ejecutores en el paquete java.util.concurrente usa pool de hilos, que consisten en hilos trabajadores. Este tipo de hilos existen separados de las tareas Runnable y Callable, el las ejecuta y aveces es usado para ejecutar múltiples tareas.


Usar hilos trabajadores minimiza la sobrecarga debida a la creación de tareas. Objetos Thread usan una suma significativa de memoria y en una aplicación de gran escala, la obtención y liberación de muchos objetos hilo crea una carga significativa de memoria.


Un tipo común de pool es el pool fijo de hilos. Este tipo de pool siempre tiene un determinado número de hilos corriendo; si un hilo es de alguna manera terminado mientras aún está en uso, es automáticamente reemplazado con un nuevo hilo. Tareas son enviadas al pool vía una cola interna, la cual mantiene tareas extra siempre que hay más tareas activas que hilos.


Una ventaja importante de tener un pool fijo de hilos es que aplicaciones que lo usan se degradan de manera elegante. Para entender esto, considere un servidor de aplicaciones web donde cada petición HTTP es manejada por un hilo separado. Si la aplicación simplemente crea un nuevo hilo por cada nueva petición HTTP, y el sistema recibe más peticiones de las que puede manejar, la aplicación va a parar repentinamente de responder a todas las peticiones cuando la sobrecarga de todos esos hilos sobrepase la capacidad del sistema. Con un límite en el número de hilos que pueden ser creados, la aplicación no va a servir cada petición cada vez que llegue, pero va a continuar sirviendo tan rápido como el sistema pueda.


Una manera simple para crear un ejecutor que use un pool con número fijo de hilos es invocar el método fabrica newFixedThreadPool en la clase java.util.concurrent.Executors. Esta clase también ofrece los siguientes métodos de fábrica:


  • El método  newCachedThreadPool crea un ejecutor con un pool de hilos que puede crecer. Este ejecutor es útil para aplicaciones que lanzan muchas tareas de corta duración.
  • El método newSingleThreadExecutor crea un ejecutor que ejecuta una tarea individual a la vez.
  • Muchos métodos fábrica son versiones de ScheduledExecutorService.


Si ninguno de los ejecutores dados por los métodos fábrica que vimos sirve a sus necesidades, construir instancias de java.util.concurrent.ThreadPoolExecutor o java.util.concurrent.ScheduledThreadPoolExecutor le va a dar opciones adicionales.


Fork / Join

El framework fork/join es una implementación de la interface ExecutorService que ayuda a tomar las ventajas de múltiples procesadores. Está diseñado para trabajo que puede ser dividido recursivamente en piezas más pequeñas. La meta es usar todo el poder de procesamiento disponible para mejorar el desempeño de su aplicación.


Como con cualquier implementación de ExcecutorService, el framework fork/join distribuye tareas a hilos trabajadores en un pool de hilos. El framework fork/join es distinto porque usa un algoritmo robo de trabajo (work-stealing).  Los hilos trabajadores que corren sin cosas que hacer pueden robar tareas de otros hilos que todavía están ocupados.


El centro del framework fork/join es la clase ForkJoinPool, una extensión de la clase AbstractExecutorService .


ForkJoinPool implementa el núcleo del algoritmo de robo de trabajo y puede ejecutar procesos ForkJoinTask.


Uso básico



El primer paso para usar el framework fork/join es escribir el código que va a ejecutar el segmento de trabajo. Su código debería verse similar al siguiente seudo código:


if (mi porción de trabajo es suficientemente pequeño)
hacer el trabajo directamente
else
dividir el trabajo en dos piezas
invocar las dos piezas y esperar por los resultados


Envuelva este código en una subclase ForkJoinTask, típicamente usando una de sus  más especializados tipos, cualquiera de RecursiveTask (que retorna un resultado) o  RecursiveAction.


Después que su subclase ForkJoinTask esté lista, cree un objeto que represente todo el trabajo a realizar y pasarlo al método invoke() de una instancia ForkJoinPool.


Desenfocando para mayor claridad

Para ayudar a entender cómo trabaja el framework fork/join, vamos a considerar el siguiente ejemplo. Suponga que usted quiere desenfocar una imagen. La fuente original de la imagen es representada por un arreglo de enteros, donde cada entero contiene los valores de color por cada pixel. La imagen desenfocada de destino es también representada por un arreglo de enteros con el mismo tamaño.
Realizar el desenfoque se logra trabajando a través del arreglo de enteros un pixel a la vez. Cada pixel es promediado con sus pixeles circundantes ( los componentes rojo,  verde y azul son promediados), y el resultado es puesto en el arreglo destino. Ya que una imagen es un arreglo grande, este proceso puede tomar largo tiempo. Usted puede tomar ventaja de el procesamiento concurrente o sistemas multiprocesador implementando el algoritmo usando el framework fork/join. Esta es una posible implementación:
public class ForkBlur extends RecursiveAction {
   private int[] mSource;
   private int mStart;
   private int mLength;
   private int[] mDestination;
 
   // Processing window size; should be odd.
   private int mBlurWidth = 15;
 
   public ForkBlur(int[] src, int start, int length, int[] dst) {
       mSource = src;
       mStart = start;
       mLength = length;
       mDestination = dst;
   }

   protected void computeDirectly() {
       int sidePixels = (mBlurWidth - 1) / 2;
       for (int index = mStart; index < mStart + mLength; index++) {
           // Calculate average.
           float rt = 0, gt = 0, bt = 0;
           for (int mi = -sidePixels; mi <= sidePixels; mi++) {
               int mindex = Math.min(Math.max(mi + index, 0),
                                   mSource.length - 1);
               int pixel = mSource[mindex];
               rt += (float)((pixel & 0x00ff0000) >> 16)
                     / mBlurWidth;
               gt += (float)((pixel & 0x0000ff00) >>  8)
                     / mBlurWidth;
               bt += (float)((pixel & 0x000000ff) >>  0)
                     / mBlurWidth;
           }
         
           // Reassemble destination pixel.
           int dpixel = (0xff000000     ) |
                  (((int)rt) << 16) |
                  (((int)gt) <<  8) |
                  (((int)bt) <<  0);
           mDestination[index] = dpixel;
       }
   }
 
 ...


Ahora implemente el método abstracto compute(), el cual realiza las dos cosas desenfocar directamente y dividir en dos tareas más pequeñas. Un umbral simple de longitud de arreglo ayuda a determinar si el trabajo se realiza o se separa en tareas.


protected static int sThreshold = 100000;

protected void compute() {
   if (mLength < sThreshold) {
       computeDirectly();
       return;
   }
   
   int split = mLength / 2;
   
   invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
             new ForkBlur(mSource, mStart + split, mLength - split,
                          mDestination));
}


Si los métodos previos están en una subclase de RecursiveAction, entonces configurar la tarea para correr en un ForkJoinPool es sencillo, e involucra los siguientes pasos:


  1. Crear una tarea que representa todo el trabajo a realizarse.
// source image pixels are in src
// destination image pixels are in dst
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
  1. Crear el ForkJoinPool que va a correr la tarea.
ForkJoinPool pool = new ForkJoinPool();
  1. Correr la tarea.
    1. pool.infoke(fb);


Para el código fuente completo, incluyendo algún código extra que crea el archivo de la imagen final, vea el ejemplo ForkBlur.

Implementaciones estándar.



Además usando el framework fork/join para implementar algoritmos personalizados para tareas a ser ejecutadas de forma concurrente en un sistema multiprocesador (como el ejemplo ForkBlur.java en la sección previa), hay características generalmente útiles en Java SE que ya están implementadas en el framework fork/join. Una de esas implementaciones, introducida en Java SE 8, es usada por la clase java.util.Arrays para sus métodos parallelShort(). Estos métodos son similares a sort(), pero apalancan la concurrencia vía el framework fork/join. Ordenamiento paralelo de grandes cantidades de arreglos es más rápido que ordenamiento secuencial cuando corres en sistemas multiprocesadores. Sin embargo, cómo exactamente se usa el framework para realizar esta tarea, sale del alcance de este Tutorial de Java. Para esta información, revise la documentación del API de Java.


Otra implementación del framework fork/join es usada por los métodos en el paquete java.util.streams, que es parte del proyecto Lambda programado para la liberación de Java SE 8. Para más información vaya a la sección Lambda Expressions


Colecciones concurrentes

El paquete java.util.concurrent incluye un número de adiciones al Framework Java Collections. Estas están categorizadas más fácilmente por las interfaces proporcionadas:
  • BlockingQueue :Define una estructura de datos First-in-First-out (FIFO) que bloquea o pausa cuando intentas agregar a una cola llena, o retirar de una cola vacía.
  • ConcurrentMap es una subinterface de java.util.Map que define operaciones atómicas útiles. Estas operaciones eliminan o reemplazan un par clave-valor sólo si la clave está presente, o agrega un par clave-valor sólo si la clave está ausente. Hacer estas operaciones atómicas ayuda a evitar sincronización. La implementación estándar de uso general de ConcurrentMap es ConcurrentHashMap, la cual es una análoga concurrente de HashMap.
  • ConcurrentNavigableMap es una subinterface de ConcurrentMap  que soporta coincidencias aproximadas. La implementación estándar de propósito general de ConcurrentNavigableMap es ConcurrentSkipListMap, la cual es un análogo concurrente de TreeMap.
Todas estas colecciones ayudan a evitar Errores de consistencia de memoria definiendo relaciones sucede-antes entre una operación que adiciona un objeto a la colección con sus subsecuentes operaciones que acceder o removerlo.


Variables atómicas

El paquete java.util.concurrent.atomic  define clases que soportan operaciones atómicas en variables individuales. Todas las clases tienen métodos get y set que trabajan tal como leer o escribir en variables volatile. Esto quiere decir, un set tiene una relación sucede-antes con cualquier subsecuente get sobre la misma variable. El método atómico compareAndSet también tiene estas características de consistencia de memoria, como lo hacen los métodos aritméticos atómicos simples que aplican a variables atómicas enteras.


Para ver cómo debería ser usado este paquete, vamos a volver sobre la clase Counter que originalmente usamos para demostrar la interface hilo:


class Counter {
   private int c = 0;

   public void increment() {
       c++;
   }

   public void decrement() {
       c--;
   }

   public int value() {
       return c;
   }

}


Una forma para hacer Counter seguro para para el uso desde hilos es hacer sus métodos sincronizados tal como SynchronizedCounter:


class SynchronizedCounter {
   private int c = 0;

   public synchronized void increment() {
       c++;
   }

   public synchronized void decrement() {
       c--;
   }

   public synchronized int value() {
       return c;
   }

}


Para esta clase simple, la sincronización es una solución aceptable. Pero para clases más complicadas, nosotros podríamos querer evitar el impacto liveness de la sincronización innecesaria. Reemplazando el campo int con AtomicInteger nos permite prevenir interferencia entre hilos sin acudir a la sincronización, como en  AtomicCounter:
import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
   private AtomicInteger c = new AtomicInteger(0);

   public void increment() {
       c.incrementAndGet();
   }

   public void decrement() {
       c.decrementAndGet();
   }

   public int value() {
       return c.get();
   }

}

Números aleatorios concurrentes

En JDK 7, java.util.concurrent incluye una clase conveniente, ThreadLocalRandom,  para aplicaciones que esperan usar números aleatorios desde múltiples hilos o ForkJoinTasks.
Para acceso concurrente, usando ThreadLocalRandom en lugar de Math.random()resulta en menos contención, y finalmente en mejor desempeño.
Todo lo que usted necesita hacer es llamar ThreadLocalRandom.current(), Entonces llame uno de sus métodos para obtener un número aleatorio. Aquí hay un ejemplo:
int r = ThreadLocalRandom.current() .nextInt(4, 77);

0 comments:

Popular Posts

Pedro Rozo. Powered by Blogger.