Si ncroni zaçã o de Threads Si s tema j us to quando cada threadobtém aces s os ufici ente a recurs osl i mi tadosa fim de prog redi r razoavel mente Starvati on ocorre quando uma ou mai sthreadses tã oi mpedi dasde obter aces s o a um recurs o, nã o podendo, cons equentemente, prog redi r Deadl ock Starvati on ao má xi mo: ocorre quando duasou mai sthreadses tã oà es pera numa condi çã o que nã o pode s er s ati s fei ta Sincronização de Threads Exemplo do Produtor/ Consumidor Produtor: public class Producer extends Thread { private CubbyHole cubbyhole; private int number; public Producer(CubbyHole c, int number) { cubbyhole = c; this.number = number; } public void run() { for (int i = 0; i < 10; i++) { cubbyhole.put(i); System.out.println("Producer #" + this.number + " put: " + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } } } Sincronização de Threads Consumidor: public class Consumer extends Thread { private CubbyHole cubbyhole; private int number; public Consumer(CubbyHole c, int number) { cubbyhole = c; this.number = number; } public void run() { int value = 0; for (int i = 0; i < 10; i++) { value = cubbyhole.get(); System.out.println("Consumer #" + this.number + " got: " + value); } } } Sincronização de Threads Nem o produtor nem o consumidor se esforçam para que o consumidor obtenha apenas 1 valor produzido uma única vez A sincronização ocorre a baixo ní vel, nos métodos put( )e get( )do CubbyHole Problema #1: o produtor é mais rápido que o consumidor: produtor gera 2 números antes que o consumidor consiga um Consumer Producer Producer Consumer #1 #1 #1 #1 got: put: put: got: 3 4 5 5 Problema #2: o consumidor é mais rápido que o produtor: consome o mesmo valor duas vezes Producer Consumer Consumer Producer #1 #1 #1 #1 put: got: got: put: 4 4 4 5 Produtor/Consumidor Copie de http://java. sun. com/docs/books/tutorial/essential/ threads/example/ProducerConsumerTest. java ,o programa que cria um objecto CubbyHole, um Produtor, um Consumidor e lança o produtor e consumidor Verifique o seu output Produtor/Consumidor public class CubbyHole { private int contents; private boolean available = false; public synchronized int get() { o consumidor obtém o trinco do CubbyHole ... } o consumidor liberta o trinco do CubbyHole public synchronized void put(int value) { o produtor obtém o trinco do CubbyHole ... } } o produtor liberta o trinco do CubbyHole notifyAll() e wait() O método wait() larga o trinco mantido pelo consumidor no CubbyHole (permitindo assim que o produtor readquira o trinco) e fica à espera de uma notificação por parte do produtor O método notifyAll() acorda todas as threads em espera no objecto em causa (neste caso, o CubbyHole). As threads acordadas competem pelo trinco Uma dessas threads obtém o trinco, as outras voltam a esperar A classe Object também define o método notify(), o qual acorda arbitrariamente uma das threads em espera sobre esse objecto Semáforos Introduzidos em 1965 por Dijkstra Semáforos são inteiros não-negativos que apenas podem ser acedidos por duas operações: P() (ou wait() ou Esperar) if (sem > 0) { sem = sem-1 } else { block until sem > 0 } V() (ou signal() ou Assinalar) sem = sem+1 P() e V() devem ser atómicos Semáforos em Java public class Semaphore { private int count = 0; public Semaphore(int initVal) { count = initVal; } public synchronized void P() { if (count <= 0) try { /* esperar até que count > 0 */ wait(); } catch (InterruptedException e) { } count--; } public synchronized void V() { count++; notify(); /* acordar quem estiver em espera */ } } Receita para sincronização em Java Usar variáveis (p.e. booleanas) para definir as condições apropriadas Proteger todas as variáveis de condição com métodos public synchronized Se a chamada a um método tem de ser bloqueada devido a uma condição não ser verdadeira, a thread chamante faz wait() Se a chamada a um método torna uma condição sobre a qual outras threads possam estar à espera verdadeira, a thread chamante faz notify() ou notifyAll() Exemplo: BoundedBuffer class BoundedBuf { private Vector buf = new Vector(); private Semaphore mutex = new Semaphore(1); private Semaphore space_avail = new Semaphore(100); private Semaphore item_avail = new Semaphore(0); public void put(int item) { space_avail.P(); mutex.P(); buf.addElement(item); mutex.V(); item_avail.V(); } public int get() { item_avail.P(); mutex.P(); int item = buf.removeElementAt(0); mutex.V(); space_avail.V(); return item; } } Exercícios Considere um parque de estacionamento. Programe em Java a classe carParkControl, que gere as entradas e saídas de carros (métodos depart() e arrive()). A capacidade do parque é dada como parâ metro. As classes Arrivals e Departures efectuam a entrada e saída de um carro. Exercícios Considere um banco com uma só conta. Nessa conta é possível efectuar as operações de depositar e levantar. Cada uma destas operações tem um parâmetro inteiro com o valor a depositar ou a levantar. A operação de depositar consiste unicamente em somar o valor ao saldo actual. A operação de levantar verifica se o saldo é suficiente para satisfazer o levantamento e em caso afirmativo subtrai o valor ao saldo. Considere ainda que ambas as operações podem ser efectuadas por vários processos em simultâneo. Escreva o código em Java das operações depositar e levantar. Elabore sobre a necessidade de sincronização entre os vários processos que executam as operações.