Sunteți pe pagina 1din 55

Sistemas Operativos

Concurrencia en Java
Luis Gajardo lgajardo@ubiobio.cl

DESDE JAVA 1.4

HEBRAS EN JAVA
Creacin de hebras Especificar el comportamiento de una hebra: Escribir la clase A que implementa la interfaz Runnable y redefinir el mtodo run()

Crear el objeto que representa el comportamiento:


Runnable runnable= new A();

Crear el controlador de la hebra:


Thread thread= new Thread(runnable);

Lanzar la hebra:
thread.start();

ESPECIFICAR EL COMPORTAMIENTO
Ejemplo:
class SomeBehavior implements Runnable { String name;
SomeBehavior(String name) { this.name= name; } public void run() { for (int i= 0; ; i++) System.out.println(name+": i= "+i); } }

CREACION DE LA HEBRA
Ejemplo:
public class Example { public static void main(String[] args) { Runnable r= new SomeBehavior("A"); Thread t= new Thread(r); t.start(); for (int i= 0; i<4; i++) System.out.println("main: i= "+i); } }

EJECUCION
Ejemplo:
% java Example main: i= 0 A: i= 0 main: i= 1 A: i= 1 main: i= 2 main: i= 3 A: i= 2 A: i= 3 A: i= 4 A: i= 5 ... (nunca termina) ...

La salida de ambas hebras puede mezclarse


Una aplicacin en Java termina cuando todas sus hebras terminan

El mtodo main termina, pero no la hebra adicional


Nunca se obtiene el %"

ESPERAR A QUE UNA HEBRA TERMINE


Una hebra termina cuando el mtodo run() termina, o cuando uno de sus mtodos lanza una excepcin no capturada Una segunda hebra puede esperar que la hebra t termine invocando:
t.join();

Si t ya termin, join retorna de inmediato Incomodidad: join lanza InterruptedException

EJEMPLO: Clculo de Fibonacci


// Clculo de un nmero de Fibonacci secuencialmente static int seqFib(int n) { if (n<=2) return 1; else return seqFib(n-1)+seqFib(n-2); } // Clculo de Fibonacci concurrente static int dualFib(int n) { try { if (n<=20) return seqFib(n); else { FibCalculator calc= new FibCalculator(n-2); Thread t= new Thread(calc); t.start(); int partialRes= seqFib(n-1); t.join(); // lanza InterruptedException return partialRes + calc.res; } } catch (InterruptedException excp) { return 0; } }

EJEMPLO: Clculo de Fibonacci


// Comportamiento de la hebra static class FibCalculator implements Runnable { int n; int res; FibCalculator(int n) { this.n= n; }

public void run() { res= seqFib(n); }


}

EJEMPLO: Clculo de Fibonacci


Observaciones: Calcular fib concurrentemente para n<=20 es intil: - Crear una hebra es tan caro como calcular fib(20). Recomendacin: - Evite el enredo de cdigo debido a la captura de excepciones en la mitad de un mtodo, coloque la instruccin try ... catch en el nivel ms amplio del mtodo

PATRONES DE LA CONCURRENCIA
Patrn Futuros
Una mejor estructura para calcular fib en paralelo:
static int dualFib(int n) { if (n<=20) return seqFib(n); else { FutureFib future= new FutureFib(n-2); int partialRes= seqFib(n-1); return partialRes + future.get(); } }

PATRONES DE LA CONCURRENCIA
Patrn Futuros (continuacin)
class FutureFib implements Runnable { int n; Thread t; int res;
FutureFib(int n) { this.n= n; t= new Thread(this); t.start(); } public void run() { res= seqFib(n); } public int get() { try { t.join(); return res; } catch (InterruptedException excp) { return 0; } } }

PATRONES DE LA CONCURRENCIA
Patrn Futuros: Generalizacin
class FutureComputation implements Runnable { declarar parmetros tipo-retorno res; Thread t; FutureComputation(parmetros) { asignar parmetros t= new Thread(this); t.start(); } public void run() { res= calcular } tipo-retorno get() { try { t.join(); return res; } catch (InterruptedException excp) { ... } } }

PATRONES DE LA CONCURRENCIA
Patrn Futuros: Ejercicio propuesto factorial Implementar el mtodo: static
double factorial(int n){ ... }

factorial(n)= 1*2*3* ... * (n-1) * n calcular (n/2+1) * ... * (n-1) * n como un futuro. Usted puede usar:
static double seqProd(int i, int j) { double res= 1.0; for (int k= i; k<=j; k++) res*= k; return res; }

CREACION DE HEBRAS
Mecanismo alternativo Extender la clase Thread para especificar el comportamiento de la hebra en el mismo controlador de la hebra:
class MyThread extends Thread { ... public void run() { //... actividades de la hebra ... } }

Crear el controlador de la hebra:


MyThread thread= new MyThread();

Lanzar la hebra:
thread.start();

MONITORES EN JAVA
En Java, cada objeto es conceptualmente un monitor. Invariante: Una sola hebra puede poseer el monitor Un monitor es solicitado y devuelto mediante la instruccin especial:
synchronized ( expresin ) { /*cuerpo*/ }

La evaluacin de expresin entrega la referencia del objeto que constituye el monitor.

MONITORES EN JAVA
Semntica de la instruccin synchronized Una hebra T ejecuta la instruccin synchronized de la siguiente manera: T evala expresin. Sea M el monitor referenciado por la expresin. T solicita el monitor M. T podra tener que esperar, debido a otras hebras compitiendo por M. T ejecuta el cuerpo de la instruccin synchronized. T devuelve el monitor M. T posee el monitor M mientras se ejecuta el cuerpo o cualquier mtodo invocado desde el cuerpo Java garantiza que M siempre se devuelve cuando se sale de la instruccin synchronized M es otorgado en orden de llegada

MONITORES EN JAVA
Devolucin temporal del monitor
En ocasiones una operacin no puede ejecutarse mientras no se verifique una condicin.

Para postergar una operacin:


M.wait(); // El monitor es devuelto

Para notificar que una condicin podra verificarse:


M.notifyAll(); // Se retoman todas las hebras postergadas

El thread que invoca M.notifyAll() mantiene la propiedad del monitor

MONITORES EN JAVA
Reactivacin de hebras postergadas Todos las hebras que invocaron M.wait() son retomadas. Pero antes de continuar solicitan el monitor M. Ejemplo:
Object get() { synchronized (this) { try { while (list.isEmpty()) this.wait(); // lanza InterruptedException return list.remove(0); } catch (InterruptedException excp) { return null; } } }

PATRON DE SINCRONIZACION: GUARDIAS


Un guardia es una condicin que debe verificar un objeto para que una operacin pueda ejecutarse. La mayora de los problemas de sincronizacin pueden modelarse a partir de guardias. Ejemplos de guardias: La operacin get slo puede ejecutar cuando se verifica: !list.isEmpty() La operacin put slo puede ejecutar cuando se verifica: list.size()<MAXSIZE

PATRON DE SINCRONIZACION: GUARDIAS


El buffer correcto

class Buffer { List list= new ArrayList(); Synchronized void put(Object item) { try { while (list.size()>=MAXSIZE) wait(); list.add(item); notifyAll(); } catch(InterruptedException e) { } } synchronized Object get() { try { while (list.isEmpty()) wait(); Object item= list.remove(0); notifyAll(); return item; } catch(InterruptedException e) { return null; } } }

PATRON DE SINCRONIZACION: GUARDIAS


Generalizacin: Guardias Cuando requiera sincronizacin, trate de usar el siguiente patrn:
synchronized tipo nombre-mtodo(parmetros) { try { while (! guardia) wait(); ... ejecute operacin ... notifyAll(); } catch (InterruptedException excp) { return ...; } }

OBSERVACIONES SOBRE NotifyAll()


Cuando la operacin es siempre ejecutable, no coloque el while No invoque notifyAll cuando puede probar que ninguna guardia va a cambiar. notifyAll() retoma todas las hebras postergadas. Parece ineficiente y es ineficiente. Es posible retomar una sola hebra?

No use notify
notify() retoma una sola hebra, no sabemos cul. Es muy difcil ver la condicin de borde que har que su solucin no funcione, pero existe. notifyAll() es ineficiente pero no se puede evitar. Es un defecto de diseo de Java Los monitores de Java se inspiran de las regiones crticas de P. Brinch Hansen (1972). J. Gosling olvid que Car Hoare mejor los monitores en 1974 JDK 1.5 trae los monitores de Hoare.
Nota: J. Gosling es el creador de Java

SOLUCION PROBLEMA DE LOS FILOSOFOS


Los 5 filsofos son representados por 5 hebras. Hay un monitor controlando el acceso a los palitos. El controlador
class Controller { boolean[] state= new boolean[5];
Solicitudes de palitos Hebrasfilsofo

synchronized void take(int k) { controlador try { while (state[k]) public void run() { wait(); for (;;) { state[k]= true; int first= Math.min(i, (i+1)%5); } int last= Math.max(i, (i+1)%5); catch(InterruptedException e){ ctrl.take(first); } ctrl.take(last); } eat(i, (i+1)%5); synchronized void leave(int k) { ctrl.leave(i); state[k]= false; ctrl.leave((i+1)%5); notifyAll(); think(); } } } } Sea consiente que en esta solucin los filsofos pueden sufrir hambruna

monitor

DESDE JAVA 1.5

CONCURRENCIA EN JAVA 1.5


La plataforma Java 2 incluye nuevas utilidades para el manejo de concurrencia, desde la versin 1.5. Se han aadido los paquetes: java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks Estos paquetes permiten crear diversas estructuras de hilos, como: pooling de hilos o colas de bloqueos, liberando al programador del control "a mano". En definitiva, se da soporte para automatizar la programacin concurrente.

FRAMEWORK EXECUTOR
Un Executor es un objeto que ejecuta tareas de tipo Runnable.
Es similar a la invocacin:
new Thread(aRunnableObject).start();

Dos observaciones: La creacin de threads puede ser costosa Un mecanismo ptimo para la mantencin de threads es difcil de implementar. Solucin: El nuevo framework Executor resuelve todos estos problemas separando la utilizacin del thread de la forma en cmo este debe ser creado. Adems permite estandarizar la invocacin, planificacin, ejecucin y control de tareas asncronas segn un conjunto de polticas de ejecucin.

FRAMEWORK EXECUTOR
Creando un Executor
Crearlo usando un mtodo factory de la clase Executors. Por ejemplo:

Executors.newCachedThreadPool() Crea un pool que va a ir creando threads conforme se vayan necesitando, pero puede reutilizar threads inactivos creados anteriormente.
Executors.newFixedThreadPool(int numThreads) Crea un pool con el nmero de threads indicado; dichos threads siempre estarn listos para procesar tareas. El pool maneja tambin una cola de tareas; cada thread toma una tarea de la cola y la procesa, al terminar contina con otra tarea de la cola hasta que no queden ms. Otros executors ScheduledThreadPool, SingleThreadExecutor()...

FRAMEWORK EXECUTOR
Usando un Executor
Por ejemplo:
Executor executor = Executors.newFixedThreadPool(5); executor.execute (new RunnableTask1()); executor.execute (new RunnableTask2());

Para detener (ordenadamente) todos los threads en ejecucin, usar:


executor.shutdown();

Otros mtodos de inters:


shutdownNow() awaitTermination()

FRAMEWORK EXECUTOR
Ejemplo: un simple pool de threads (conjunto de hilos) con un tamao por
defecto de 2 y un mximo de 4 hilos. El mtodo shutdown() termina todos los hilos si no hay tareas en ejecucin. Por lo tanto, Executor libera al programador de la gestin de los threads y nos permite centrarnos en la funcionalidad.
public class SimplePooledExecutorSample { private ThreadPoolExecutor executor; private int defaultThreadCount=2; private int maxThreadCount=4; private int keepAliveTime=100; private int MAX_PENDING_TASKS=100; private BlockingQueue pendingTasksQueue= new ArrayBlockingQueue(MAX_PENDING_TASKS); public SimplePooledExecutorSample() { executor = new ThreadPoolExecutor(defaultThreadCount,maxThreadCount, keepAliveTime,TimeUnit.MILLISECONDS,pendingTasksQueue ); }

public void createMultipleProducerThreads(int producerThreadCount ) { Producer c= new Producer(); for (int i=0; i < producerThreadCount; i++ ){ executor.execute(c); } }

FRAMEWORK EXECUTOR
public void createMultipleConsumerThreads(int consumerThreadCount ) { Consumer c= new Consumer(); for (int i=0; i < consumerThreadCount; i++ ){ executor.execute(c); } } public void shutdown(){ executor.shutdown(); }

public static void main(String[] args ){ SimplePooledExecutorSample sample= new SimplePooledExecutorSample(); sample.createMultipleProducerThreads(3); sample.createMultipleConsumerThreads(3); sample.createMultipleProducerThreads(3); sample.shutdown(); }

FRAMEWORK EXECUTOR
class Consumer implements Runnable { private int runCount= 1; public void run() { System.out.println("Consuming..." + runCount++ +" ThreadId:" + Thread.currentThread().getId()); } } class Producer implements Runnable { private int runCount= 1;

public void run() { for (int i=0;i<10000;i++) { //algo de actividad para mantener ocupada a la CPU.. double x=(double)100.89*(double)124.99; } System.out.println("Producing..." + runCount++ +" ThreadId:" + Thread.currentThread().getId()); }
}

COLECCIONES CONCURRENTES
Una coleccin es un objeto que agrupa mltiples elementos en una sola unidad.
Las colecciones se usan para almacenar, recuperar, manipular y comunicar agregacin de datos. Normalmente representan tem de datos que forma un grupo natural, tal y como una baraja de cartas, carpeta de correos o directorio telefnico. Collection es la interface genrica en java que permite realizar esta agrupacin. Java 1.4 posee variadas colecciones, pero no pueden ser utilizadas en entornos concurrentes a no ser que el programador se preocupe de la sincronizacin.

COLECCIONES CONCURRENTES
Java 1.5 implementa colecciones concurrentes que permiten el acceso concurrente sin que el programador se preocupe de ello. La interface BlockingQueue posee las siguientes implementaciones: ArrayBlockingQueue: Cola implementada con un arreglo que posee lmite de capacidad. DelayQueue: Cola en la cual los elementos pueden retirarse cuando su delay (tiempo de espera) ha expirado. LinkedBlockingQueue: Cola implementada con lista enlazada, con lmite opcional de capacidad.

PriorityBlockingQueue: cola de prioridad que ordena los elementos automticamente segn prioridad.
SynchronousQueue: cola sincronizada, slo realiza la operacin cuando es posible, sino espera.

COLECCIONES CONCURRENTES
Los mtodos declarados en la interfaz BlockingQueue: Para aadir elementos add() : agrega un elemento offer() : devuelve false si est llena, no genera excepcin put() : se bloquea si la cola est llena Para extraer elementos remove() : extrae el elemento del principio. Genera excepcin si vaco poll() : igual que remove() pero si la cola est vaca devuelve null take() : se bloquea si la cola est vaca drainTo() : vaca la cola y devuelve coleccin Para consultar sin extraer element() : devuelve, sin extraerlo, el primer elemento. Excep. si vaca peek() : igual que element() pero devuelve null si cola vaca

COLECCIONES CONCURRENTES
Ejemplo:
Implementacin del problema del productor - consumidor

class Producer implements Runnable { private final BlockingQueue queue;


Producer(BlockingQueue q) { queue= q } public void run() { try { while (true) q.put(produce()); } catch (InterruptedException ex) { ... } } Object produce() {...}

class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue= q } public void run() { try { while (true) consume(q.take()); } catch (InterruptedException ex) { ... } } void consume(Object x) {...} }

COLECCIONES CONCURRENTES
class Ejemplo { void main() { //implementacin especfica de la cola BlockingQueue q= new LinkedQueueImplementation(); Producer p= new Producer(q); Consumer c1= new Consumer(q); Consumer c2= new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }

VARIABLES ATOMICAS
(java.util.concurrent.atomic) Son clases para manipular atmicamente variables individuales (tipos primitivos o referenciados) permitiendo el uso de mtodos para aritmtica atmica y asignacin de variables (test-and-set) de alto rendimiento.
import java.util.concurrent.atomic.AtomicInteger; class AtomicCounter { private AtomicInteger c= new AtomicInteger(0); public void increment() { c.incrementAndGet(); } } public int value() { return c.get(); } public void decrement() { c.decrementAndGet(); }

OTROS MECANISMOS DE SINCRONIZACION


Existen otras herramientas para sincronizar procesos y proteger secciones crticas del cdigo:

Semaphore semforos
Locks countDownLatch CyclicBarrier Exchanger

SEMAPHORE
Posee el mismo funcionamiento que los semforos tradicionales, permite controlar el nmero de procesos que acceden a la seccin crtica. Crear el semforo: Semaphore(int tickets)
Crea el semforo con el nmero de tickets indicado.

(java.util.concurrent.Semaphore)

Semaphore(int tickets, boolean justicia)


Crea el semforo con el nmero de tickets indicado. Si el parmetro justicia es true, el orden de atencin de los threads en espera ser FIFO sino no existe certeza sobre cual thread ser retomado.

Operaciones de sincronizacin sobre el semforo:

acquire()
Saca un ticket del semforo si hay disponibles, sino bloquea el thread hasta que exista uno disponible. Lanza la excepcin InterruptedException.

release()
Aporta un ticket al semforo, pero nunca bloquea el thread.

SEMAPHORE
Ejemplo:
import java.util.concurrent.Semaphore ; class Example extends Thread { int id; static Semaphore semaphore = new Semaphore(1); public Example(int id) { this.id= id; } public void run() { try { semaphore.acquire(); Solicita un ticket // SECCION CRITICA semaphore.release(); } catch (InterruptedException e) {} Devuelve un ticket } public static void main(String[] args) { Example e1= new Example(1); Example e2= new Example(2); e1.start(); e2.start(); }}

Crea el semforo con un ticket

SEMAPHORE
Otros mtodos: tryAcquire()
Este mtodo es no bloqueante. Solicita un ticket, sino est disponible retorna de inmediato al thread que lo invoca. Retorna un valor booleano que indica si tuvo xito (true) o no (false).

tryAcquire(long timeout, TimeUnit unit)


Este mtodo es no bloqueante. Solicita un ticket, sino est disponible espera el tiempo especificado en timeout para obtenerlo. Si al cabo de la espera no se ha obtenido un ticket retorna al thread que lo invoca. El parmetro unit puede ser SECONDS, MILLISECONDS, NANOSECONDS.

Ejemplo:
boolean exito; try { exito= semaphore.tryAcquire(50, TimeUnit.MILLISECONDS); } catch (InterruptedException e) {} if (exito) { // SECCION CRITICA semaphore.release(); }

CYCLIC BARRIER
Este mecanismo permite establecer puntos de espera comunes para varios threads. Se debe indicar a cuantos threads se esperan antes de continuar. Cada thread que llega al punto de espera debe esperar a que el resto de los threads llegue. La clase CyclicBarrier permite implementar esta funcionalidad en Java. Cuando llega el ltimo thread, todos se desbloquean y pueden continuar. Eventualmente tambin se puede lanzar un nuevo thread que realice alguna tarea (mediante la interfaz Runnable). Dado que son cclicas pueden ser utilizadas varias veces (varios puntos de espera).

CYCLIC BARRIER
Los mtodos son:
public CyclicBarrier(int parties);

Crea una nueva CyclicBarrier la cual se levantar (o desbloquear) cuando terminen los hilos (parties) que se esperan.
public CyclicBarrier(int parties, Runnable barrierAction);

Igual que el anterior, pero adems ejecuta una accin al cumplirse la barrera.
public int await() throws InterruptedException, BrokenBarrierException

Espera a todos los hilos que han invocado await() sobre la barrera.
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException

Igual que el anterior, pero aqu es posible definir un tiempo de espera mximo (timeout). El parmetro unit indica la unidad en la cual se ha expresado timeout.

CYCLIC BARRIER
Ejemplo:
int THREADS = 5; CyclicBarrier barrier = new CyclicBarrier(THREADS); ... barrier.await(); //seccin crtica, continua solo cuando 5 threads invoquen await() barrier.await(); //otra seccin crtica

LOCKS REENTRANTES
(java.util.concurrent.locks.ReentrantLock) Se introduce un mecanismo de sincronizacin alternativo al lock normal que se define a travs de la clase ReentrantLock y cuya funcionalidad se define a travs de la interfaz Lock.

El ReentrantLock se introduce por las limitaciones del lock normal: - No es posible interrumpir un thread que espera un wait. - No es posible intentar de forma no bloqueante adquirir un lock sin suspenderse definitivamente en l. - Los locks intrnsecos deben ser liberados en el mismo bloque de cdigo en el cual se suspendi.

LOCKS REENTRANTES
Comparacin: - El Lock normal conduce a un estilo de programacin sencillo, seguro y compatible con la gestin de excepciones. - El ReentrantLock conduce a estrategias menos seguras, pero ms flexibles, proporciona mayor vivacidad y mejores caractersticas de respuesta.

LOCKS REENTRANTES
La interface Lock provee varios mtodos novedosos:
public interface Lock{ void lock(); // solicitar el lock boolean tryLock(); // adquirir el lock solo si esta libre al momento de solicitarlo. // El thread no se bloquea. Retorna true si el lock fue adquirido. boolean tryLock(long timeout, TimeUnit unit) throws InterruptedExcetion; // adquirir el lock solo si esta libre en el tiempo especificado. // El thread no se bloquea. Retorna true si el lock fue adquirido.

void unlock(); // liberar el lock


Condition newCondition(); //Crea una nueva variable de condicin asociada al lock }

LOCKS REENTRANTES
A diferencia del lock normal, la interface Lock ofrece la toma de un lock: incondicional, no bloqueante, temporizado o interrumpible. Adems todas las operaciones de suspensin y liberacin de un lock son explcitas.

Ejemplo seccin crtica basada en un Lock explcito:


Lock control = new ReentrantLock(); ... control.lock(); try{ // Actualiza al objeto protegido por el lock // Atiende excepciones y restaura invariantes }finally{ control.unlock(); }

si es necesario

La liberacin debe hacerse en una sentencia finally, ya que hay que preveer la posibilidad de una excepcin, y en este caso el lock debe de liberarse explcitamente tambin.

LOCKS REENTRANTES Y VARIABLES DE CONDICION


Cuando se utiliza un Lock explcito para definir una regin asncrona, dentro de ella se utilizan los objetos Condition como mecanismo de sincronizacin entre threads. Esto es similar a un Monitor. Un objeto Condition est estructuralmente ligado a un objeto Lock. Slo puede crearse invocando el mtodo new Condition() sobre un objeto Lock. El objeto Condition solo puede ser invocado por un thread que previamente haya tomado el Lock al que pertenece.

LOCKS REENTRANTES Y VARIABLES DE CONDICION


La interface Condition provee los siguientes mtodos:
public interface Condition { void await(); // causa la espera del thread sobre una variable de condicin

boolean await(long time, TimeUnit unit); // causa la espera del thread, por el tiempo especificado, sobre // una variable de condicin. No se bloquea. Retorna false cuando // no se bloquea.
void signal(); // Notifica una condicin cumplida, despierta solo un thread void signalAll(); // Notifica una condicin cumplida. Despierta a todos los threads ...

LOCKS REENTRANTES Y VARIABLES DE CONDICION


Ejemplo: Productor - Consumidor
class BoundedBuffer { ... final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { ... try { ... } finally{ ... } }

LOCKS REENTRANTES Y VARIABLES DE CONDICION


Ejemplo: Productor - Consumidor
public Object take() throws InterruptedException { ... try { ... } finally { ... } }
} //fin de la clase

LOCKS REENTRANTES Y VARIABLES DE CONDICION


Ejemplo: Productor - Consumidor
class BoundedBuffer {
final Lock lock= new ReentrantLock(); final ConditionnotFull= lock.new Condition(); final ConditionnotEmpty = lock.new Condition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while(count== items.length) notFull.await(); items[putptr] = x; putptr= (puptr+1) % items.length; count= count+1; notEmpty.signal(); } finally{ lock.unlock(); } }

LOCKS REENTRANTES Y VARIABLES DE CONDICION


Ejemplo: Productor - Consumidor
public Object take() throws InterruptedException { lock.lock(); try { while(count== 0) notEmpty.await(); Object x= items[takeptr]; takeptr= (takeptr+1) % items.length; count= count-1; notFull.signal(); return x; } finally { lock.unlock(); } } } //fin de la clase

S-ar putea să vă placă și