// Relacionamento Produtor-Consumidor com sincronização usando Locks. // O consumidor consome corretamente um valor, apenas depois de o produtor ter produzido um valor. // O produtor produz corretamente um novo valor, apenas depois do consumidor ter consumido um valor produzido. // Threads que acessam o objeto compartilhado, não estão cientes de que estão sendo sincronizadas. // O código que realiza a sincronização é colocado nos métodos get e set do buffer sincronizado (objeto compartilhado). // Os métodos run de Producer e de Consumer, simplesmente chamam os métodos set e get do buffer compartilhado. // // Pacote usado: java.util.concurrent.locks : // Este pacote contém Interfaces and classes provendo um framework for locking (bloqueio) and // espera por condições, diferentes da sincronização embutida em monitores. // // Interface: Lock // Lock implementations provide more extensive locking operations than // can be obtained using synchronized methods. // Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } // A lock is a tool for controlling access to a shared resource by multiple threads. // Commonly, a lock provides exclusive access to a shared resource: only one thread at a time can // acquire the lock and all access to the shared resource requires that the lock be acquired first. // Where a Lock replaces the use of synchronized methods. // // Interface Condition // a Condition replaces the use of the Object monitor methods. // A Condition instance is intrinsically bound to a lock. To obtain a Condition instance // for a particular Lock instance use its newCondition() method. // // Uma função é reentrante se, enquanto estiver sendo executada, puder se re-invocada por si mesmo // ou por outras rotinas, interrompendo a execução atual por um instante. // Classe: ReentrantLock // A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit // monitor lock accessed using synchronized methods and statements, but with extended capabilities. // // Fig. 23.11: SynchronizedBuffer.java // SynchronizedBuffer sincroniza acesso a um único inteiro compartilhado. import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class SynchronizedBuffer implements Buffer { // Bloqueio para controlar sincronização com esse buffer private Lock accessLock = new ReentrantLock(); // condições para controlar leitura e gravação private Condition canWrite = accessLock.newCondition(); private Condition canRead = accessLock.newCondition(); private int buffer = -1; // compartilhado pelas threads Produtor e Consumidor private boolean occupied = false; // se o buffer estiver ocupado // coloca o valor int no buffer public void set( int value ) { accessLock.lock(); // bloqueia esse objeto // envia informações de thread e de buffer para a saída, então espera try { // enquanto o buffer não estiver vazio, coloca thread no estado de espera while ( occupied ) { System.out.println( "Producer tries to write." ); displayState( "Buffer full. Producer waits." ); canWrite.await(); // espera até que o buffer esteja vazio } // fim do while buffer = value; // configura novo valor de buffer // indica que a produtora não pode armazenar outro valor // até a consumidora recuperar valor atual de buffer occupied = true; displayState( "Producer writes " + buffer ); // sinaliza a thread que está esperando para ler a partir do buffer canRead.signal(); } // fim do try catch ( InterruptedException exception ) { exception.printStackTrace(); } // fim do catch finally { accessLock.unlock(); // desbloqueia esse objeto } // fim de finally } // fim do método set // retorna valor do buffer public int get() { int readValue = 0; // inicializa de valor lido a partir do buffer accessLock.lock(); // bloqueia esse objeto // envia informações de thread e de buffer para a saída, então espera try { // enquanto os dados não são lidos, coloca thread em estado de espera while ( !occupied ) { System.out.println( "Consumer tries to read." ); displayState( "Buffer empty. Consumer waits." ); canRead.await(); // espera até o buffer tornar-se cheio } // fim do while // indica que a produtora pode armazenar outro valor // porque a consumidora acabou de recuperar o valor do buffer occupied = false; readValue = buffer; // recupera o valor do buffer displayState( "Consumer reads " + readValue ); // sinaliza a thread que está esperando o buffer tornar-se vazio canWrite.signal(); } // fim do try // se a thread na espera tiver sido interrompida, imprime o rastreamento de pilha catch ( InterruptedException exception ) { exception.printStackTrace(); } // fim do catch finally { accessLock.unlock(); // desbloqueia esse objeto } // fim de finally return readValue; } // fim do método get // exibe a operação atual e o estado de buffer public void displayState( String operation ) { System.out.printf( "%-40s%d\t\t%b\n\n", operation, buffer, occupied ); } // fim do método displayState } // fim da classe SynchronizedBuffer // // Fig. 23.7: Producer.java // O método run do Producer armazena os valores de 1 a 10 no buffer. import java.util.Random; public class Producer implements Runnable { private static Random generator = new Random(); private Buffer sharedLocation; // referência a objeto compartilhado // construtor public Producer( Buffer shared ) { sharedLocation = shared; } // fim do construtor Producer // armazena os valores de 1 a 10 em sharedLocation public void run() { int sum = 0; for ( int count = 1; count <= 10; count++ ) { try // dorme de 0 a 3 segundos, então coloca valor em Buffer { Thread.sleep( generator.nextInt( 3000 ) ); // thread sleep sharedLocation.set( count ); // configura valor no buffer sum += count; // incrementa soma de valores System.out.printf( "\t%2d\n", sum ); } // fim do try // se a thread adormecida é interrompida, imprime rastreamento de pilha catch ( InterruptedException exception ) { exception.printStackTrace(); } // fim do catch } // fim do for System.out.printf( "\n%s\n%s\n", "Producer done producing.", "Terminating Producer." ); } // fim do método run } // fim da classe Producer // // Fig. 23.8: Consumer.java // oO método run de Consumer itera dez vezes lendo um valor do buffer. import java.util.Random; public class Consumer implements Runnable { private static Random generator = new Random(); private Buffer sharedLocation; // referência a objeto compartilhado // construtor public Consumer( Buffer shared ) { sharedLocation = shared; } // fim do construtor Consumer // lê o valor do sharedLocation quatro vezes e soma os valores public void run() { int sum = 0; for ( int count = 1; count <= 10; count++ ) { // dorme de 0 a 3 segundos, lê o valor do buffer e adiciona a soma try { Thread.sleep( generator.nextInt( 3000 ) ); sum += sharedLocation.get(); System.out.printf( "\t\t\t%2d\n", sum ); } // fim do try // se a thread adormecida é interrompida, imprime rastreamento de pilha catch ( InterruptedException exception ) { exception.printStackTrace(); } // fim do catch } // fim do for System.out.printf( "\n%s %d.\n%s\n", "Consumer read values totaling", sum, "Terminating Consumer." ); } // fim do método run } // fim da classe Consumer