Tema 1: Introducción

Anuncio
Tema 12: Programación
multihilo
Antonio J. Sierra
Índice
1. Modelo de hilo en Java.
2. El hilo principal.
3. Creación de un hilo.
4. Creación de múltiples hilos. Prioridades.
5. Comunicación entre hilos. Sincronización.
6. Modelado UML para la programación
multihilo. Clases activas.
1
Introducción
• Un programa multihilo contiene dos o más partes
que pueden ejecutarse de forma concurrente.
• Cada parte de ese programa se llama hilo
(Thread) y cada hilo establece un camino de
ejecución independiente.
• La concurrencia reúne varios hilos de ejecución.
• Forma especializada de multitarea (multitasking).
– basada en procesos
– basada en hilos
Multitarea basada en Procesos
• Proceso es un programa que se está ejecutando.
• Multitarea basada en procesos se puede decir que es la
característica que le permite a la computadora ejecutar dos
o más programas concurrentemente.
• Un programa es la unidad de código más pequeña que el
planificador puede seleccionar.
• Los procesos son tareas pesadas que necesitan su propio
espacio de direccionamiento. La comunicación entre
procesos es más cara y limitada.
• Es costoso el cambio de contexto de un proceso a otro.
2
Multitarea basada en hilos
• El hilo es la unidad de código más pequeña que se
puede seleccionar.
• La multitarea basada en hilos requiere menos
sobrecarga que la multitarea basada en procesos.
• Los hilos son más ligeros, ya que comparten el
mismo espacio de direcciones y comparten
cooperativamente el mismo proceso pesado.
• La comunicación entre hilos es ligera y el cambio
de contexto de un hilo al siguiente es menos
costoso.
El modelo de hilo en Java
• Java utiliza hilos para permitir que el
entorno en su globalidad sea asíncrono.
3
Estados de los hilos
• Un hilo puede estar ejecutándose.
• Puede estar preparado para ejecutarse tan pronto como
disponga de tiempo de CPU.
• Si se está ejecutando puede suspenderse, lo que equivale a
detener temporalmente su actividad.
• El hilo suspendido puede reanudarse permitiendo que
continúe su tarea allí donde la dejó.
• Un hilo puede estar bloqueado cuando espera un recurso.
• Un hilo puede detenerse, finalizando su ejecución de
manera inmediata. Una vez detenido, un hilo no puede
reanudarse.
El ciclo de vida de un Thread
En ejecución
start
Nuevo Hilo
yield
Ejecución
Suspendido
El método run termina
Detenido
4
Prioridades de los hilos
• El intérprete de Java utiliza prioridades para
determinar cómo debe tratar cada hilo con
respecto a los demás.
• La prioridad de un hilo es un valor entero que
asigna un orden de ejecución cuando los hilos
estén preparados para ejecutarse o ejecutándose
• La prioridad de un hilo se utiliza para decidir
cuándo se pasa a ejecutar otro hilo.
– Esto es lo que se conoce como cambio de contexto.
Reglas para el cambio de
contexto
• Un hilo puede ceder voluntariamente el control.
– Esto se hace por abandono explícito, al quedarse
dormido o al bloquearse en espera de una E/S
pendiente. En este caso, se examinan todos los hilos
restantes y se selecciona para su asignación a la CPU
aquél que, estando listo para su ejecución, tenga la
prioridad más alta.
• Un hilo puede ser desalojado por otro con
prioridad más alta.
– En este caso, un hilo de baja prioridad que no libera la
CPU es desalojado por otro de mayor prioridad con
independencia de lo que estuviese haciendo en ese
instante.
5
Sincronización (I)
• Los hilos permiten y potencian el comportamiento
asíncrono de los programas,
– forma de forzar el sincronismo donde sea necesario
– Haciendo que coincidan en el tiempo dos o más hilos de ejecución
• Java implementa una versión de modelo clásico de
sincronización entre procesos, llamado monitor.
• El monitor es un mecanismo de control que fue definido
en primer lugar por C.A.R. Hoare y que puede entenderse
como una pequeña caja en la que sólo cabe un hilo.
– Una vez que un hilo entra en el monitor, los demás deben esperar a
que éste salga.
– Los monitores se utilizan para proteger un bien compartido y evitar
que sea manipulado por más de un hilo simultáneamente.
Sincronización (II)
• Cada objeto tiene su propio monitor
implícito en el que entra automáticamente
cuando se llama a uno de los métodos
sincronizados del objeto.
• Una vez que un hilo está dentro de un
método sincronizado, ningún otro hilo
puede llamar a otro método sincronizado
del mismo objeto.
6
El hilo principal
class HiloActual {
public static void main (String args[]) {
Thread t = Thread.currentThread();
System.out.println("Hilo actual: " +t);
//cambia el nombre del hilo
t.setName("Mi hilo");
System.out.println("después del cambio de nombre: " +t);
try {
for (int n = 5; n>0 ; n--) {
System.out.println(n);
Thread.sleep(1000);
}
}catch (InterruptedException e){
System.out.println("Interrupcion del hilo principal");
}
Hilo actual: Thread[main,5,main]
}
después del cambio de nombre: Thread[Mi hilo,5,main]
5
4
3
2
1
}
Creación de un hilo
• Dos opciones:
– Implementando la interfaz Runnable.
– Extendiendo la clase Thread.
• La clase Thread define varios métodos que
pueden sobrescribir las clases derivadas.
• El único que tiene que ser sobrescrito es run().
– Este método es exactamente el mismo que es necesario
para implementar la interfaz Runnable.
• Implementar la interfaz permite utilizar herencia
de cualquier otra clase diferente.
7
Implementando la interfaz
Runnable
• Si un objeto implementa la interfaz
Runnable se puede usar para crear un
hilo.
• El comienzo del hilo (con start()) provoca
que el método run() del hilo se pueda
invocar de forma separada.
Thread(Runnable objetoHilo, String nombreHilo)
synchronized void start()
public abstract void run()
Ejemplo con Runnable (I)
//Crea un segundo hilo.
class NuevoHilo implements Runnable {
Thread t;
NuevoHilo() {
//Crea un nuevo hilo
t = new Thread(this,"Hilo hijo");
System.out.println("Hilo hijo: "+t);
t.start();
//comienza el hilo
}
//Este es el punto de entrada del segundo hilo
public void run() {
try {
for (int i = 5; i > 0; i--){
System.out.println("Hilo hijo: " +i);
Thread.sleep(500);
}
}catch(InterruptedException e) {
System.out.println("Interrupcion de hilo hijo");
}
System.out.println("Sale del hilo hijo");
}
}
8
Ejemplo con Runnable (II)
class Hilos0 {
public static void main(String args[]){
new NuevoHilo();
//crea un nuevo hilo
try{
for (int i = 5; i>0 ; i--){
System.out.println("Hilo Principal: " +i);
Thread.sleep(1000);
}
} catch(InterruptedException e) {
System.out.println("Interrupcion del hilo principal");
}
System.out.println("Sale del hilo principal.");
}
}
Extendiendo la clase Thread
• Crea una nueva clase que herede de la clase
Thread y después crear una instancia de esa
clase.
• Esta nueva clase debe sobreescribir el método
run(), que es el punto de entrada del nuevo hilo.
• También debe llamar al método start() para
que comience la ejecución del nuevo hilo.
9
Ejemplo con Thread (I)
//Crea un hilo extendiendo la clase Thread.
class NuevoHilo extends Thread {
NuevoHilo() {
//Crea un nuevo hilo
super("Hilo demo");
System.out.println("Hilo hijo: "+this);
start();
//comienza el hilo
}
//Este es el punto de entrada del segundo hilo
public void run() {
try {
for (int i = 5; i > 0; i--){
System.out.println("Hilo hijo: " +i);
Thread.sleep(500);
}
}catch(InterruptedException e) {
System.out.println("Interrupcion de hilo hijo");
}
System.out.println("Sale del hilo hijo");
}
}
Ejemplo con Thread (II)
class Hilos1 {
public static void main(String args[]){
new NuevoHilo();//crea un nuevo hilo
try{
for (int i = 5; i>0 ; i--){
System.out.println("Hilo Principal: " +i);
Thread.sleep(1000);
}
} catch(InterruptedException e) {
System.out.println("Interrupcion del hilo principal");
}
System.out.println("Sale del hilo principal.");
}
}
10
Creación de múltiples hilos
• Hasta ahora sólo se han utilizado dos hilos:
– el hilo principal
– y un hilo hijo.
• Se pueden generar tantos hilos como
necesiten.
Ejemplo, creación de varios hilos (I)
//Creación de múltiples hilos
class NuevoHilo implements Runnable {
String nombre;
Thread t;
NuevoHilo(String NombreHilo) { //Crea un nuevo hilo
nombre = NombreHilo;
t = new Thread(this, nombre);
System.out.println("Nuevo Hilo: " + t);
t.start();
//comienza el hilo
}
//Este es el punto de entrada del hilo
public void run() {
try {
for (int i = 5; i > 0; i--){
System.out.println(nombre + ": " +i);
Thread.sleep(1000);
}
}catch(InterruptedException e) {
System.out.println("Interrupcion del hilo "+ nombre);
}
System.out.println("Sale del hilo " + nombre);
}
}
11
Ejemplo creación de varios hilos (II)
C:\jdk1.2.2\bin>java Hilos2
Nuevo Hilo: Thread[Uno,5,main]
Nuevo Hilo: Thread[Dos,5,main]
Nuevo Hilo: Thread[Tres,5,main]
class Hilos2 {
Uno: 5
public static void main(String args[]){ Dos: 5
new NuevoHilo("Uno");
Tres: 5
new NuevoHilo("Dos");
Uno: 4
new NuevoHilo("Tres");
Dos: 4
Tres: 4
try{
Uno: 3
Thread.sleep(10000);
Dos: 3
} catch(InterruptedException e) {
Tres: 3
System.out.println(
Uno: 2
"Interrupcion del hilo principal"); Dos: 2
}
Tres: 2
System.out.println(
Uno: 1
"Sale del hilo principal.");
Dos: 1
Tres: 1
}
Sale del hilo Uno
}
Sale del hilo Dos
Sale del hilo Tres
Sale del hilo principal.
Comunicación entre hilos
•
Una forma de determinar si un hilo ha terminado de ejecutarse es llamando al
método de la clase Thread isAlive():
final boolean isAlive() throws InterruptedException
– Devuelve true si el hilo al que se hace referencia está todavía ejecutándose.
– Devuelve false en caso contrario.
•
El método join() se utiliza para esperar la finalización de un hilo.
final void join() throws InterruptedException
– Este método espera hasta que finalice el hilo sobre el que se llama.
– Su nombre surge de la idea de que el hilo llamante espera hasta que el hilo
especificado se reúne con él.
– Hay otras formas de join() que permiten especificar el tiempo máximo que se
quiere esperar la finalización de un hilo.
final void join(long millis) throws InterruptedException
final void join(long millis, int nanos) throws
InterruptedException
12
Ejemplo de Comunicación entre
hilos (I)
//Uso del método join() para esperar la finalización de hilos
class NuevoHilo implements Runnable {
String nombre;
Thread t;
NuevoHilo(String NombreHilo) {
//Crea un nuevo hilo
nombre = NombreHilo;
t = new Thread(this, nombre);
System.out.println("Nuevo Hilo: " + t);
t.start();
//comienza el hilo
}
//Este es el punto de entrada del hilo
public void run() {
try {
for (int i = 5; i > 0; i--){
System.out.println(nombre + ": " +i);
Thread.sleep(1000);
}
}catch(InterruptedException e) {
System.out.println("Interrupcion del hilo "+ nombre);
}
System.out.println("Sale del hilo " + nombre);
}
}
Ejemplo de Comunicación entre
hilos (II)
class Hilos3{
public static void main(String args[]){
NuevoHilo ob1 = new NuevoHilo("Uno");
NuevoHilo ob2 = new NuevoHilo("Dos");
NuevoHilo ob3 = new NuevoHilo("Tres");
System.out.println("El hilo Uno está vivo: "+ob1.t.isAlive());
System.out.println("El hilo Dos está vivo: "+ob2.t.isAlive());
System.out.println("El hilo Tres está vivo: "+ob3.t.isAlive());
//espera a que terminen los otros hilos
try{
System.out.println("Espera finalización de otros hilos ");
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupcion del hilo principal");
}
System.out.println("El hilo Uno está vivo: "+ob1.t.isAlive());
System.out.println("El hilo Dos está vivo: "+ob2.t.isAlive());
System.out.println("El hilo Tres está vivo: "+ob3.t.isAlive());
System.out.println("Sale del hilo principal ");
}
}
La salida de este programa es la siguiente:
C:\jdk1.2.2\bin>java Hilos3
Nuevo Hilo: Thread[Uno,5,main]
Nuevo Hilo: Thread[Dos,5,main]
Nuevo Hilo: Thread[Tres,5,main]
El hilo Uno estß vivo: true
El hilo Dos estß vivo: true
El hilo Tres estß vivo: true
Espera finalizaci_n de otros hilos
Uno: 4
Dos: 4
Tres: 4
Uno: 3
Dos: 3
Tres: 3
Uno: 2
Dos: 2
Tres: 2
Uno: 1
Dos: 1
Tres: 1
Sale del hilo Uno
Sale del hilo Dos
Sale del hilo Tres
El hilo Uno estß vivo: false
El hilo Dos estß vivo: false
El hilo Tres estß vivo: false
Sale del hilo principal
13
Suspensión y Reanudación de un
hilo
•
Son dos métodos marcados como “Deprecated”:
final void resume()
final void suspend()
•
La clase Object proporciona los siguientes métodos:
– void wait()
– void wait(long timeout)
– void wait(long timeout, int nanos)
• Provoca que el hilo actual espere hasta que otro hilo invoque a notify() o
notifyAll().
– void notify ()
• Despierta un solo hilo que estaba esparando en este monitor del objeto.
– void notifyAll ()
• Despierta todos los hilos que estaban esperando en este monitor del objeto.
Prioridad
• El planificador de hilos utiliza las prioridades de los hilos
para determinar cuándo debe permitir que se ejecute cada
hilo.
• Si dos hilos están preparados para ejecutarse se ejecutará el
de mayor prioridad.
• Prioridad es un valor entero (5 por defecto) comprendido
entre Thread.MIN_PRIORITY y
Thread.MAX_PRIORITY.
• Se puede gestionar mediante los métodos:
final void setPriority(int nivel)
final int getPriority ()
14
Sincronización
•
Cuando dos o más hilos necesitan acceder de manera simultánea a un recurso
compartido, necesitan asegurarse de que sólo uno de ellos accede al mismo en
un instante dado. El proceso mediante el cual se consigue esto se llama
sincronización.
•
Un monitor es un objeto que se utiliza como cerrojo exclusivo, o mutex
(mutually exclusive, mutuamente exclusivo).
•
Cuando un hilo adquiere un cerrojo, se dice que ha entrado en el monitor. Los
restantes hilos que estuviesen intentando acceder al monitor bloqueado quedan
en suspensión hasta que el primer hilo salga del monitor.
Se dice que estos hilos están esperando al monitor. Un hilo que posea un
monitor puede volver a acceder al mismo si así lo desea.
La sincronización del código se puede realizar mediante la palabra clave
synchronized (sincronizado).
– Java proporciona un soporte único, a nivel de lenguaje, para la sincronización.
– Sólo uno de los hilos puede ser el propietario del monitor en un instante dado.
•
•
Ejemplo sin sincronización
//Este programa no está sincronizado.
class Llamada {
void llama(String msg){
System.out.print("["+msg);
try {
Thread.sleep(1000);
}catch(InterruptedException e){
System.out.println("Interrumpido");
}
System.out.print("]");
}
}
class ElQueLlama implements Runnable {
String msg;
Llamada objetivo;
Thread t;
public ElQueLlama(Llamada objet, String s) {
objetivo = objet;
msg = s;
t = new Thread(this);
t.start();
}
public void run() {
objetivo.llama(msg);
}
}
class Sincro0{
public static void main(String[] args){
Llamada objetivo
ElQueLlama ob1 =
ElQueLlama ob2 =
ElQueLlama ob3 =
"Sincronizado");
= new Llamada();
new ElQueLlama(objetivo, "Hola");
new ElQueLlama(objetivo, "Mundo");
new ElQueLlama(objetivo,
//Espera a que los hilos terminen
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
}catch(InterruptedException e){
System.out.println("Interrumpido");
}
}
}
La salida producida por este programa.
[Hola[Mundo[Sincronizado]]]
15
Con Sincronización
class Llamada {
synchronized void llama(String msg){
//…
}
• Esto evitará que otros métodos puedan acceder a llama() mienta otro
lo está utilizando.
• La salida
[Hola]
[Mundo]
[Sincronizado]
•
Otra forma es sincronizar un objeto en un conjunto de sentencias.
synchronized (objeto){
//Sentencias que deben ir sincronizadas
}
Modelado UML para la
programación multihilo.
Clases activas.
• Las clases activas son elementos estructurales de los
bloques del construcción del modelo conceptual de UML.
• Una clase activa es una clase cuyos objetos tienen uno o
más procesos o hilos que constituyen flujos de control
independientes pero concurrentes con otros flujos de
control (con los que muy probablemente se deberán
sincronizar).
• Una clase activa es igual que una clase, excepto en que sus
objetos representan elementos cuyo comportamiento es
concurrente con otros elementos.
16
Modelado UML para la
programación multihilo.
Clases activas.
GestorEventos
supender()
17
Descargar