5 cosas que usted no sabía acerca de... java.util.concurrent, Parte 2

La programación concurrente significa trabajar de una forma más inteligente, no trabajar más duro

Además de las colecciones que facilitan la concurrencia, java.util.concurrent introdujo otros componentes pre-compilados que pueden ayudarle a regular y ejecutar hebras en aplicaciones multihebras. Ted Neward presenta otros cinco elementos que se deben tener sobre programación Java™ , del paquete java.util.concurrent .

Ted Neward, Director, Neward & Associates

Ted NewardTed Neward es el director de Neward & Associates, donde hace consultoría, es mentor, enseña y hace presentaciones sobre Java, .NET, XML Services y otras plataformas. Vive en Seattle, Washington.



15-10-2012

Las Colecciones concurrentes facilitan la programación concurrente al proporcionar estructuras de datos de hebra segura y bien optimizadas. Sin embargo, en algunos casos los desarrolladores necesitan ir un paso más allá y pensar en la regulación y/o aceleración de la ejecución de hebras. Dado que el sentido central de java.util.concurrent es simplificar la programación multihebra, es posible esperar que el paquete incluya herramientas de sincronización — y las incluye.

Este artículo, que es un seguimiento a la Parte 1, introduce varias construcciones de sincronización que están en un nivel más alto que los primitivos del lenguaje central (supervisores), pero no tan alto como para que sean encerradas dentro de una clase de Collection. Usar estos bloqueos y puertas es bastante simple una vez que usted sabe para qué se utilizan.

Acerca de esta serie

¿Así que usted considera que sabe acerca de programación Java? El hecho es que la mayoría de los desarrolladores rasguñan la superficie de la plataforma Java, aprendiendo apenas lo necesario para realizar su trabajo. En estaserie, Ted Neward profundiza hacia el núcleo de la funcionalidad de la plataforma Java para descubrir datos poco conocidos que pueden ayudarle a resolver incluso los desafíos de programación más complicados.

1. Semáforo

En algunos sistemas corporativos, no es poco común que los desarrolladores necesiten regular el número de solicitudes abiertas (hebras/acciones) frente a un recurso en particular — de hecho, la regulación puede en algunas ocasiones mejorar el rendimiento de un sistema al reducir la cantidad de contención frente a ese recurso en particular. Aunque realmente es posible intentar escribir el código de regulación manualmente, es mucho más fácil usar clases semáforo, las cuales se encargan de la regulación por usted, como se muestra en el Listado 1:

Listado 1. Use Semáforo para regular
import java.util.*;
import java.util.concurrent.*;

public class SemApp
{
    public static void main(String[] args)
    {
        Runnable limitedCall = new Runnable() {
            final Random rand = new Random();
            final Semaphore available = new Semaphore(3);
            int count = 0;
            public void run()
            {
                int time = rand.nextInt(15);
                int num = count++;

                try
                {
                    available.acquire();

                    System.out.println("Executing " +
                        "long-running action for " +
                        time + " seconds... #" + num);

                    Thread.sleep(time * 1000);

                    System.out.println("Done with #" +
                        num + "!");

                    available.release();
                }
                catch (InterruptedException intEx)
                {
                    intEx.printStackTrace();
                }
            }
        };

        for (int i=0; i<10; i++)
            new Thread(limitedCall).start();
    }
}

Incluso aunque las 10 hebras de esta muestra se están ejecutando (lo cual es posible verificar ejecutando jstack contra el proceso Java en ejecuciónSemApp), solo tres están activas. Las otras siete están contenidas en el compartimento hasta que uno de los contadores semáforo se libere. (En realidad, la clase Semaphore soporta la adquisición y liberación de más de un permit al mismo tiempo, pero ello no tendría sentido en este escenario).


2. CountDownLatch

Desarrolle habilidades de este tema

Este contenido es parte de knowledge paths progresivo para avanzar en sus habilidades. Vea:

Si Semaphore es la clase de concurrencia diseñada para permitir que "ingresen" hebras, una a la vez (tal vez evocando recuerdos del personal de seguridad en los clubes nocturnos populares), entonces CountDownLatch es la puerta de entrada en una carrera de caballos. Esta clase contiene todas las hebras en el compartimento hasta que se cumple una condición en particular, punto en el cual las libera todas a la vez.

Listado 2. CountDownLatch: ¡Vamos a las carreras!
import java.util.*;
import java.util.concurrent.*;

class Race
{
    private Random rand = new Random();

    private int distance = rand.nextInt(250);
    private CountDownLatch start;
    private CountDownLatch finish;

    private List<String> horses = new ArrayList<String>();

    public Race(String... names)
    {
        this.horses.addAll(Arrays.asList(names));
    }

    public void run()
        throws InterruptedException
    {
        System.out.println("And the horses are stepping up to the gate...");
        final CountDownLatch start = new CountDownLatch(1);
        final CountDownLatch finish = new CountDownLatch(horses.size());
        final List<String> places =
            Collections.synchronizedList(new ArrayList<String>());

        for (final String h : horses)
        {
            new Thread(new Runnable() {
                public void run() {
                    try
                    {
                        System.out.println(h +
                            " stepping up to the gate...");
                        start.await();

                        int traveled = 0;
                        while (traveled < distance)
                        {
                            // In a 0-2 second period of time....
                            Thread.sleep(rand.nextInt(3) * 1000);

                            // ... a horse travels 0-14 lengths
                            traveled += rand.nextInt(15);
                            System.out.println(h +
                                " advanced to " + traveled + "!");
                        }
                        finish.countDown();
                        System.out.println(h +
                            " crossed the finish!");
                        places.add(h);
                    }
                    catch (InterruptedException intEx)
                    {
                        System.out.println("ABORTING RACE!!!");
                        intEx.printStackTrace();
                    }
                }
            }).start();
        }

        System.out.println("And... they're off!");
        start.countDown();

        finish.await();
        System.out.println("And we have our winners!");
        System.out.println(places.get(0) + " took the gold...");
        System.out.println(places.get(1) + " got the silver...");
        System.out.println("and " + places.get(2) + " took home the bronze.");
    }
}

public class CDLApp
{
    public static void main(String[] args)
        throws InterruptedException, java.io.IOException
    {
        System.out.println("Prepping...");
        
        Race r = new Race(
            "Beverly Takes a Bath",
            "RockerHorse",
            "Phineas",
            "Ferb",
            "Tin Cup",
            "I'm Faster Than a Monkey",
            "Glue Factory Reject"
            );

        System.out.println("It's a race of " + r.getDistance() + " lengths");

        System.out.println("Press Enter to run the race....");
        System.in.read();

        r.run();
    }
}

Note que en el Listado 2 esoCountDownLatch cumple dos propósitos: Primero, libera todas las hebras simultáneamente, simulando el inicio de la carrera, pero más adelante un mecanismo de cierre diferente simula el final de la carrera, esencialmente de forma que la hebra "principal" puede imprimir los resultados. Para una carrera con más comentarios, es posible añadir CountDownLatch al final de los puntos de "giro" y a "la mitad" de la carrera, a medida que los caballos cruzan los valores de primer cuarto, mitad y tercer cuarto de la distancia.


3. Ejecutor

Los ejemplos del Listado 1 y el Listado 2 sufren de una falla un poco frustrante, pues le fuerzan a usted a crear objetos Thread directamente. Esta es una receta para los problemas porque en algunas JMS crear un Thread es una operación de peso pesado y es mucho mejor reutilizar Thread existentes que crear otros nuevos. No obstante, en otras JVM es exactamente lo opuesto: Las Thread son bastante ligeras y es mucho mejor simplemente new-up (renovar) alguna cuando la necesite. Desde luego, si Murphy tiene su manera (que usualmente la tiene), cualquiera que sea el abordaje que usted utilice, ese será exactamente el incorrecto para la plataforma sobre la cual usted finalmente haga la implementación.

El grupo experto JSR-166 (vea Recursos) anticipó esta situación, hasta cierto punto. En lugar de hacer que los desarrolladores Java crean Threads directamente, introdujeron la interfaz Executor , una abstracción para crear nuevas hebras. Como se muestra en el Listado 3, Executor le permite crear hebras sin tener que new los objetosThread usted mismo (a):

Listado 3. Executor
Executor exec = getAnExecutorFromSomeplace();
exec.execute(new Runnable() { ... });

La principal desventaja de usar Executor es la misma que encontramos con todas las fábricas: la fábrica debe provenir de algún lugar. Desafortunadamente, a diferencia de CLR, la JVM no incluye una agrupación de hebras estándar para toda la VM.

El Executor ui-grid-ddoes sirve como un lugar común para obtener instancias que implementen Executor, pero solo tiene métodos new (para crear una nueva agrupación de hebras, por ejemplo); no tiene instancias pre-creadas. Así que usted está por su cuenta si desea crear y utilizar instancias Executor en su código. (O, en algunos casos, usted podrá usar una instancia proporcionada el contenedor/plataforma que usted seleccione).

ExecutorService, a su servicio

Aunque es muy útil no tener que preocuparse por la procedencia de las Thread, la interfaz Executor carece de algunas funcionalidades que un desarrollador Java puede esperar, como la capacidad para inicializar una hebra diseñada para producir un resultado y esperar, de una manera que no bloquee, hasta que ese resultado esté a disposición. (Esta es una necesidad común en aplicaciones de escritorio, donde un usuario ejecutará una operación IU que requiere acceder a una base de datos y que incluso podría desear cancelar la operación antes de que se complete si tarda demasiado).

Para esto, los expertos JSR-166 crearon una abstracción mucho más útil, la interfaz ExecutorService , la cual modela la fábrica de inicialización de hebras como un servicio que se puede controlar colectivamente. Por ejemplo, en lugar de llamar a execute() una vez para cada tarea, ExecutorService puede tomar una colección de tareas y retornar una List of Futures que representa los resultados futuros de cada una de dichas tareas.


4. ScheduledExecutorServices

Si bien la interfaz ExecutorService es bastante buena, ciertas tareas necesitan ejecutarse de una manera programada, como la ejecución de una tarea dada a intervalos determinados o en un momento específico. Este es el terreno de ScheduledExecutorService, la cual extiende ExecutorService.

Si su meta era crear un comando "heartbeat" que hiciera "ping" cada cinco segundos, ScheduledExecutorService podría hacerlo tan fácil como lo puede ver en el Listado 4:

Listado 4. "pings" ScheduledExecutorService programados
import java.util.concurrent.*;

public class Ping
{
    public static void main(String[] args)
    {
        ScheduledExecutorService ses =
            Executors.newScheduledThreadPool(1);
        Runnable pinger = new Runnable() {
            public void run() {
                System.out.println("PING!");
            }
        };
        ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS);
    }
}

¿Qué tal eso? Sin malgastar horas con hebras, sin malgastar tiempo pensando qué hacer si el usuario desea cancelar el "heartbeat", sin marcar hebras explícitamente como de primer o segundo plano; simplemente dejar esos detalles de planificación al ScheduledExecutorService.

A propósito, si un usuario desease cancelar el "heartbeat" el retorno del llamado scheduleAtFixedRate sería una instancia ScheduledFuture , la cual no solo se envuelve en torno al resultado si lo hay, sino que también tiene un método cancel para terminar la operación programada.


5. Métodos para tiempo de espera excedido

La capacidad para poner un tiempo de espera excedido concreto en torno a operaciones de bloqueo (para así evitar puntos muertos) es uno de los mayores avances de la biblioteca java.util.concurrent frente sus primos de concurrencia más antiguos, como son los monitores y el bloqueo.

Estos métodos casi siempre están sobrecargados con un par int/TimeUnit , lo cual indica cuánto debe esperar el método antes de intervenir y retornar el control al programa. Esto requiere más trabajo por parte del desarrollador — ¿cómo recuperaría usted si no se adquiere el bloqueo? — pero los resultados casi siempre son correctos: menos puntos muertos y más código seguro para la producción. (Para más detalles acerca de la escritura de código listo para la producción, vea Release It! de Michael Nygard enRecursos.)


En conclusión

El paquete java.util.concurrent contiene muchas más herramientas ingeniosas que se extienden más allá de las Colecciones, particularmente en los paquetes .locks y .atomic . Profundice y también encontrará útiles estructuras de control como CyclicBarrier y más.

Como muchos aspectos de la plataforma Java, usted no necesita observar demasiado para encontrar código de infraestructura que puede ser tremendamente útil. Siempre que esté escribiendo código multihebras, recuerde las herramientas tratadas aquí y en el artículo previo.

En la próxima entrega abordaremos un nuevo tema: cinco cosas que usted no conoce acerca de Jars.


Descargar

DescripciónNombretamaño
Sample code for this article5things5-src.zip10KB

Recursos

Aprender

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=tecnologia Java
ArticleID=840698
ArticleTitle=5 cosas que usted no sabía acerca de... java.util.concurrent, Parte 2
publish-date=10152012