Asignatura 780014
Programación Avanzada
Tema 6 – Tareas y patrones de diseño para concurrencia
©
[email protected]
Tareas y Threads
Por ahora:
– Programa concurrente = preparación + actividades concurrentes
– Actividad concurrente = Thread nuevo
Nueva posibilidad:
– Una actividad concurrente es una tarea independiente de las otras
• Definida como una unidad de trabajo abstracta y discreta
Concepto útil en C/S:
– Cada cliente y respuesta del servidor es una tarea
– Incrementa la capacidad de prestar servicios (throughput)
– Mejora tiempos de respuesta (responsiveness) con carga normal
– Mejora el ritmo de degradación de prestaciones con carga se elevada
Estrategias de ejecución
Estrategias para ejecutar tareas:
– Secuencial
• Bajo “throughput”, tiempo de respuesta malo
– Un Thread por tarea
• Mejor uso de recursos
• Para tareas breves, la creación/destrucción de Thread es sobrecarga
• Con carga baja buena respuesta, con carga alta peligro de agotamiento
– Un grupo de Thread a reutilizar (pool)
• Se elimina sobrecarga por creación/destrucción
• Se limitan los recursos reservados
• Se alcanza estabilidad en carga alta
Las tres estrategias en esquema visual
Estrategias erróneas
Ejemplo:
– Web server que acepta conexiones por sockets en el puerto 80
– Para cada petición crea un thread nuevo
Pocos clientes
class UnreliableWebServer
{
public static void main(String[] args)
{
ServerSocket socket = new ServerSocket(80);
while (true)
{
final Socket connection = socket.accept();
Runnable r = new Runnable() { public void run() { handleRequest(connection); } };
}
//¡No hacer esto!
new Thread(r).start();
}
}
Muchos clientes
– Sólo será capaz de atender a un número reducido de clientes
Estrategia eficaz, pool de hilos
• Estructura superior al hilo que agrupa y gestiona varios hilos
– Ejecuta tareas organizadas en una cola
• Habrá más tareas que Thread
– Cada Thread puede ejecutar una tarea cada vez
– El número de Thread puede variar según necesidades
– Se pueden repartir los pools por granjas de servidores
Algoritmo de comportamiento
de cada Thread
while (true)
{
if (no tasks) wait for a task;
execute the task;
}
Pool de hilos en java
Implementado mediante el Framework Executor:
– Conjunto de interfaces
– Ejecución concurrente y asíncrona de tareas
La base del framework es la interfaz Executor
– Describe un objeto para ejecutar Runnables
public interface Executor
{
void execute(Runnable command);
}
Flexibiliza el mantenimiento
– Permite cambios de las políticas de ejecución sin cambiar el código
Políticas de ejecución en Java
En el framework Executor podemos aplicar políticas de ejecución
– Una política de ejecución incluye:
• Definir en que Thread se ejecutan las tareas
• Definir el orden en el que se ejecutan las tareas
• El número de tareas que se permiten ejecutar concurrentemente
• El número de tareas que pueden encolarse en espera de ejecución
• Selección de la tarea que debe ser rechazada si hay sobrecarga
• Definir acciones a ejecutar antes y después de una tarea
– Una política es una herramienta de gestión de recursos.
• La óptima depende de:
– Los recursos disponibles
– La calidad de servicio (throughput) que se requiere
Ciclo de vida de los Executors
Usando la interfaz ExecutorService
– Se maneja el ciclo de vida de un servicio executor:
public interface ExecutorService extends Executor
{
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit);
// Otros métodos convenientes para iniciar tareas
}
Interfaz ExecutorService
ExecutorService ofrece
– Servicios para controlar ciclo de vida de los Thread pool
– Un thread pool sigue un ciclo de vida con tres estados:
• Running: Cuando se crea y en régimen normal de funcionamiento.
– Admite tareas y cuando tiene threads disponibles los ejecuta.
• Shutting down: Terminando gradualmente.
– No acepta nuevas tareas, pero las que están en colase ejecutan
(aunque no hayan iniciado su ejecución)
• Terminated: Finalización abrupta.
– Trata de finalizar las tareas en ejecución, y ni admite nuevas
tareas, ni comienza la ejecución de tareas en cola.
La clase ThreadPoolExecutor
class ThreadPoolExecutor implements ExecutorService
– Tiene 4 constructores
– Los cuatro tienen en común:
int corePoolSize, // Número inicial de Thread que se crean
int maximumPoolSize, // Número máximo de threads que se pueden crear
long keepAliveTime, TimeUnit unit, // Tiempo para destruir Thread que sobran hasta corePoolSize
BlockingQueue<Runnable> workQueue, //Tipo de cola de tareas
– Además podemos tener: ningún parámetro más, uno de los dos siguientes o los dos:
ThreadFactory threadFactory, // Política con las que se crean los thread dinámicamente
// (Prioridad, Grupo, naturaleza)
RejectedExecutionHandler handler) // Define la política con la que se descartan las tareas
// tareas cuya ejecución es rechazada, bien por shutdown
// o por cola de tareas limitada
Tipos de pool de hilos
Crear pools con ThreadPoolExecutor es complejo:
– Es recomendable usar los métodos de factoría:
• Executors.newCachedThreadPool()
• Executors.newFixedThreadPool(int n)
• Executors.newSingleThreadExecutor()
• Executors.newScheduledThreadPool(int n)
– Estos métodos devuelven un ExecutorService
Executors.newCachedThreadPool()
Pool de threads no está limitado
en tamaño
– Puede reutilizar threads creados
previamente cuando se quedan
libres
– Si no hay ningún thread
disponible para una tarea nueva,
se crea uno
– Los threads que no han sido
utilizados durante un minuto, se
destruyen.
Qué pasaría si…:
1.Sistema inactivo
2.Llegan 10 tareas simultáneamente
3.Pasan 10 segundos
4.Llegan 5 tareas simultáneamente
5.Pasan 5 minutos
6.Llegan 20 tareas simultáneamente.
Tareas de entre 5 y 15
segundos para procesarse.
Executors.newFixedThreadPool(int n)
Pool que reutiliza un conjunto
fijo de threads
– Con una cola compartida
de tareas
– Si termina algún thread
debido a un fallo durante la
ejecución (antes del
shutdown), se vuelve a
crear uno nuevo para
sustituirlo.
Qué pasaría si…:
1.Sistema inactivo
2.Llegan 10 tareas simultáneamente
3.Pasan 10 segundos
4.Llegan 5 tareas simultáneamente
5.Pasan 5 minutos
6.Llegan 20 tareas simultáneamente.
Tareas de entre 5 y 15
segundos para procesarse.
Executors.newSingleThreadExecutor()
Crea un pool con un único thread
– Operando sobre una cola de
tareas
– Es el modelo seguido por el Swing
event thread
– Garantiza que las tareas se
ejecutan secuencialmente
– Garantiza que solamente hay una
activa en cualquier momento
dado
Qué pasaría si…:
1.Sistema inactivo
2.Llegan 10 tareas simultáneamente
3.Pasan 10 segundos
4.Llegan 5 tareas simultáneamente
5.Pasan 5 minutos
6.Llegan 20 tareas simultáneamente.
Tareas de entre 5 y 15
segundos para procesarse.
Executors.newScheduledThreadPool(int n)
Crea crea un pool que va a ir
ejecutando tareas programadas
cada cierto tiempo
– Puede ser en un instante dado
o de manera repetitiva
– Es parecido a un timer
• Con la diferencia de que
puede tener varios threads
para poder realizar varias
tareas programadas
simultaneamente
Qué pasaría si…:
1.Sistema inactivo
2.Llegan 10 tareas simultáneamente
3.Pasan 10 segundos
4.Llegan 5 tareas simultáneamente
5.Pasan 5 minutos
6.Llegan 20 tareas simultáneamente.
Tareas de entre 5 y 15
segundos para procesarse.
Gestión de tareas correcta
Ejemplo de un web server
– Acepta conexiones por sockets en el puerto 80
– Dispone de un pool de 7 threads para atender clientes
class ReliableWebServer
{
Executor pool = Executors.newFixedThreadPool(7);
public static void main(String[] args)
{
ServerSocket socket = new ServerSocket(80);
while (true)
{
final Socket connection = socket.accept();
Runnable r = new Runnable() { public void run() {handleRequest(connection);} };
pool.execute(r);
}
}
}
Ejemplo de finalización de las tareas del pool
Suponiendo que tuviéramos un botón en la interfaz gráfica que controla el Web Server:
private void jButtonApagar (ActionEvent evt)
{
pool.shutdown(); // No deja entrar nuevas tareas
try { // Espera durante 1 min. para ver si han terminado ya
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
{
pool.shutdownNow(); // Fuerza la terminación de las tareas
//Espera otro minuto para ver si el pool ya ha terminado
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("El pool no terminó");
}
} catch (InterruptedException ie) { // (Re-)Cancela el pool
pool.shutdownNow();
Thread.currentThread().interrupt(); //Preserva el interrupt
}
}
Tareas con resultados demorados
En el Framework Executor,
– Las tareas son objetos que implementan la interfaz Runnable,
– Su ejecución en el método public void run(){…}
– Con algunas limitaciones:
Comentarios de: Tema 6 - Tareas y patrones de diseño para concurrencia - Programación Avanzada (0)
No hay comentarios