?? java15.htm
字號:
<HTML>
<HEAD>
<TITLE>Java desde Cero</TITLE>
<META NAME="GENERATOR" CONTENT="Internet Assistant for Microsoft Word 2.0z">
</HEAD>
<BODY background=/iconos/1.gif TEXT=000000 LINK=FF0000 VLINK=A62A2A>
<H1>La liebre y la tortuga (y el guepardo)<BR>
</H1>
<P>
Java dispone de un mecanismo de prioridades para los <I>threads</I>,
de modo de poder asignar más tiempo de CPU a un thread
que a otro. Típicamente se asigna una prioridad de 1 a
10 (10 es la mayor prioridad) mediante <I>setPriority</I>, como
en el ejemplo que sigue:<BR>
<PRE>
<FONT SIZE=2>public class Ejemplo21 {
static Animal tortuga;
static Animal liebre;
static Animal guepardo;
public static void main(String argv[])
throws InterruptedException {
tortuga = new Animal(2, "T");
liebre = new Animal(3, "L");
guepardo = new Animal(4, "G");
tortuga.start();
liebre.start();
guepardo.start();
tortuga.join();
liebre.join();
guepardo.join();
}
}
class Animal extends Thread {
String nombre;
public Animal(int prioridad, String nombre) {
this.nombre = nombre;
setPriority(prioridad);
}
public void run() {
for (int x = 0; x < 30; x++) {
System.out.print( nombre );
yield();
}
System.out.println("\nLlega "+nombre );
}
}
</FONT>
</PRE>
<P>
La salida de este programa, ejecutado con <I>java Ejemplo21</I>,
es por ejemplo:<BR>
<PRE>
<FONT SIZE=2>C:\java\curso>java Ejemplo21
GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
Llega G
L<B>T</B>LLLLLLLLLLLLLLLLLLLLLLLLLLLLL
Llega L
TTTTTTTTTTTTTTTTTTTTTTTTTTTTT
Llega T
</FONT>
</PRE>
<P>
Como se ve, a pesar de haber arrancado antes la tortuga, casi
todo el tiempo de CPU lo usa primero el Guepardo, luego la Liebre
(aunque algo queda para la pobre tortuga, como se ve en la T marcada),
y finalmente para la Tortuga. No todas las corridas ni todos los
sistemas dan igual salida, ya que ésta depende de la carga
del procesador y de la implementación de Java particular.
<P>
Este programa simplemente crea tres animales (clase <FONT FACE="Arial">Animal</FONT>),
asigna un thread a cada uno y los ejecuta. Este ejemplo está
hecho en base a uno del libro "Programación Java"
de Macary y Nicolas.
<H2>Sincronicemos los relojes</H2>
<P>
Un problema básico del multithreading es cuando varios
programas (o, para el caso, varios threads) acceden a los mismos
datos: ¿cómo sabemos si uno de ellos no los modifica
mientras los está usando otro?.
<P>
Veamos un ejemplo, donde suponemos que varios threads usan la
variable <FONT FACE="Arial">valorImportante</FONT>:<BR>
<PRE>
<FONT SIZE=2> if (valorImportante > 0 ) {
..... algo se procesa acá ........
<B> valorImportante = valorImportante - 1;
</B> ..... sigue.....................
}<BR>
</FONT>
</PRE>
<P>
¿Cómo nos aseguramos que <FONT FACE="Arial">valorImportante</FONT>
no cambió entre el <FONT FACE="Arial">if</FONT> y la línea
resaltada? Otros threads pueden haberlo modificado mientras tanto.
Asimismo, puede suceder que dos threads estén ejecutando
la misma porción de código, y se pierda uno de los
decrementos. Imaginen algo así:
<PRE>
<FONT SIZE=2> (antes) valorImportante = 10
(thread 1) lee valorImportante = 10
(thread 2) lee valorImportante = 10
(thread 1) 10 -1 = 9
(thread 2) 10 -1 = 9
(thread 2) asigna 9 a valorImportante
(thread 1) asigna 9 a valorImportante
(después) valorImportante = 9</FONT>
</PRE>
<P>
Como vemos, a pesar de haber restado dos veces, hemos perdido
una de las restas. Aunque usemos -= en vez de la resta es lo mismo,
porque el código igualmente se resuelve en varios pasos
(varias <I>operaciones atómicas</I>).
<P>
Para evitar esto, Java nos brinda la palabra clave Synchronized,
que bloquea el acceso a una variable a todos los threads menos
el que lo está usando.
<P>
Vamos a ver un caso específico; se trata de dos contadores
que usan el mismo sumador para sumar de a uno una cantidad <FONT FACE="Arial">a</FONT>.
Supuestamente entre los dos deben llevar el sumador (<FONT FACE="Arial">a</FONT>)
hasta 20000.<BR>
<PRE>
<FONT SIZE=2>// Archivo Ejemplo22.java, compilar con javac Ejemplo22.java, ejecutar con java Ejemplo22
public class Ejemplo22 {
public static void main(String argv[]) {
Sumador A = new Sumador(); // un único sumador
Contador C1 = new Contador(A); // dos threads que lo usan...
Contador C2 = new Contador(A); // ...para sumar
C1.start();
C2.start();
try {
C1.join();
C2.join();
}
catch (Exception e) {
System.out.println(e);
}
}
}
class Contador extends Thread {
Sumador s;
Contador (Sumador sumador) {
s = sumador; // le asigno un sumador a usar
}
public void run() {
s.sumar(); // ejecuto la suma
}
}
class Sumador {
int a = 0;
public void sumar() {
for (int i=0; i<10000; i++ ) {
if ( (i % 5000) == 0 ) { // "%" da el resto de la división:
System.out.println(a); // imprimo cada 5000
}
a += 1;
}
System.out.println(a); // imprimo el final
}
}
</FONT>
</PRE>
<P>
Ejecutando esto nos da más o menos así (cada corrida
es diferente, dependiendo de cómo se "chocan"
los threads y la carga de la CPU):<BR>
<PRE>
<FONT SIZE=2>C:\java\curso>java Ejemplo22
0
87
8926
10434
14159
17855<BR>
</FONT>
</PRE>
<P>
Esto se debe justamente a lo que explicábamos al principio:
a veces los dos threads intentan ejecutar <FONT FACE="Arial">a
+= 1</FONT> simultáneamente, con lo que algunos incrementos
se pierden.
<P>
Podemos solucionar esto modificando el método <FONT FACE="Arial">run()</FONT>:
<BR>
<PRE>
<FONT SIZE=2> public void run() {
<B> synchronized (s) {
</B> s.sumar();
<B> }
</B> }<BR>
</FONT>
</PRE>
<P>
Con esto, sólo a uno de los dos threads se les permite
ejecutar s.sumar() por vez, y se evita el problema. Por supuesto,
el otro thread queda esperando, por lo que más vale no
utilizar esto con métodos muy largos ya que el programa
se puede poner lento o aún bloquearse.
<P>
La salida ahora será:<BR>
<PRE>
<FONT SIZE=2>C:\java\curso>java Ejemplo22
0 <
5000 < primer thread
10000 <
10000 (
15000 ( segundo thread
20000 (<BR>
</FONT>
</PRE>
<P>
Lo mismo logramos (y en forma más correcta) declarando
como synchronized al método <FONT FACE="Arial">sumar()</FONT>:
<PRE>
<FONT SIZE=2> public synchronized void sumar() { .............</FONT>
</PRE>
<P>
Esto es mejor porque la clase que llama a <FONT FACE="Arial">sumar()</FONT>
no necesita saber que tiene que sincronizar el objeto antes de
llamar al método, y si otros objetos (en otros threads)
lo llaman, no necesitamos preocuparnos.<BR>
<H2>Más sincronización</H2>
<P>
Otra manera de sincronizar el acceso de los threads a los métodos,
es lograr que éstos se pongan de acuerdo entre sí,
esperando uno hasta que otro realizó alguna tarea dada.
Para esto se usan los métodos <FONT FACE="Arial">wait()</FONT>
y <FONT FACE="Arial">notify()</FONT>. Cuando un thread llama a
<FONT FACE="Arial">wait()</FONT> en un método de un objeto
dado, queda detenido hasta que otro thread llame a <FONT FACE="Arial">notify()</FONT>
en algún método del mismo objeto.
<P>
Por ejemplo, vamos a suponer cuatro empleados que se encuentran
con su jefe y lo saludan, pero sólo luego de que éste
los salude primero.<BR>
<PRE>
<FONT SIZE=2>public class Ejemplo23 {
public static void main(String argv[]) {
Saludo hola = new Saludo();
Personal pablo = new Personal(hola, "Pablo", false);
Personal luis = new Personal(hola, "Luis", false);
Personal andrea = new Personal(hola, "Andrea", false);
Personal pedro = new Personal(hola, "Pedro", false);
Personal jefe = new Personal(hola, "JEFE", true);
pablo.start();
luis.start();
andrea.start();
pedro.start();
jefe.start();
try {
pablo.join();
luis.join();
andrea.join();
pedro.join();
jefe.join();
}
catch (Exception e) {
System.out.println(e);
}
}
}
class Saludo {
synchronized void esperarJefe(String empleado) {
try {
wait();
System.out.println(empleado+"> Buenos dias jefe!");
}
catch (InterruptedException e) {
System.out.println(e.toString());
}
}
synchronized void saludoJefe() {
System.out.println("JEFE> Buenos dias!");
notifyAll();
}
}
class Personal extends Thread {
String nombre;
Saludo saludo;
boolean esJefe;
Personal (Saludo s, String n, boolean j) {
nombre = n;
saludo = s;
esJefe = j;
}
public void run() {
System.out.println("("+nombre+" llega)");
if (esJefe)
saludo.saludoJefe();
else
saludo.esperarJefe(nombre);
}
}<BR>
</FONT>
</PRE>
<P>
Usé <FONT FACE="Arial">notifyAll()</FONT> en lugar de <FONT FACE="Arial">notify()</FONT>,
porque en el segundo caso sólo se notificaría al
primer thread (el primer empleado en llegar) y no a los demás,
que se quedarían en el <FONT FACE="Arial">wait()</FONT>.
<P>
Como se ve en la salida, a pesar de que los empleados están
en condiciones de saludar, no lo hacen hasta que no llega el jefe:
<BR>
<PRE>
<FONT SIZE=2>C:\java\curso>java Ejemplo23
(Pablo llega)
(Luis llega)
(Andrea llega)
(Pedro llega)
(JEFE llega)
JEFE> Buenos dias!
Luis> Buenos dias jefe!
Pedro> Buenos dias jefe!
Andrea> Buenos dias jefe!
Pablo> Buenos dias jefe!<BR>
</FONT>
</PRE>
<P>
Aquí hice trampa: a veces, el jefe llega y saluda antes
que alguno de los empleados, por lo que ese empleado se queda
esperando indefinidamente. Prueben de modificar las clases para
que el jefe no salude hasta que no estén todos los empleados
presentes...
<P>
Un buen ejercicio antes del próximo capítulo!<BR>
<BR>
<P>
Jorge Bourdette
<P>
<A HREF="file:///C:/WINDOWS/Personal/mailto:jpb@amarillas.com" >jpb@amarillas.com</A>
<BR>
</BODY>
</HTML>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -