Você está na página 1de 12

Introduo ao uso de Threads em Java ca

Daniel de Angelis Cordeiro danielc@ime.usp.br 26 de maro de 2004 c

Sumrio a
1 Introduo ca 1.1 O que so threads? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a 1.2 Todo programa em Java multithreaded . . . . . . . . . . . . . . . . . . . e 1.3 Por que utilizar threads? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Utilizando threads 2.1 Criando threads . . . . . 2.2 Poss veis estados de uma 2.3 Prioridades de threads . 2.3.1 Fatias de tempo 1 1 2 2 2 2 4 5 5 6 7 8 9 9 11

. . . . thread . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

3 Sincronismo entre threads 3.1 Utilizando locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Variveis volteis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a a 3.3 Quase todas as classes no so sincronizadas . . . . . . . . . . . . . . . . . a a 4 Os mtodos wait(), notify() e notifyAll() e 5 Mais Informaes co

1 Introduo ca
Este seo fornece uma breve introduo sobre threads adaptada do artigo Introduction ca ca to Java Threads [4].

1.1 O que so threads? a


Todos os sistemas operacionais modernos possuem o conceito de processos que, de forma simplicada, so programas diferentes e independentes executados pelo sistema a operacional.

Threading um artif e cio que permite a coexistncia de mltiplas atividades dentro e u de um unico processo. Java a primeira linguagem de programao a incluir explici e ca tamente o conceito de Threads na prpria linguagem. Threads so tambm chamados o a e de processos leves (lightweight processes) pois, da mesma forma que processos, threads so independentes, possuem sua prpria pilha de execuo, seu prprio program counter a o ca o e suas prprias variveis locais. Porm, threads de um mesmo processo compartilham o a e memria, descritores de arquivos (le handles) e outros atributos que so espec o a cos daquele processo. Um processo pode conter mltiplas threads que parecem executar ao mesmo tempo u e de forma ass ncrona em relao as outras threads. Todas as threads de um mesmo ca processo compartilham o mesmo espao de endereamento de memria, o que signica c c o que elas tm acesso as mesmas variveis e objetos, e que eles podem alocar novos objetos e a de um mesmo heap. Ao mesmo tempo que isso torna poss que threads compartilhem vel informaes entre si, necessrio que o desenvolvedor se assegure que uma thread no co e a a atrapalhe a execuo de outra. ca A API de threads em Java incrivelmente simples. Mas escrever um programa come plexo que as use de forma eciente e correta no to simples assim (esta uma das a e a e responsabilidade do programador impedir que uma razes da existncia de MAC 438). E o e thread interra de forma indesejvel no funcionamento das outras threads do mesmo proa cesso.

1.2 Todo programa em Java multithreaded e


Acredite, se voc escreveu algum programa em Java ento j fez um programa mule a a tithreaded. Todo programa em Java possui pelo menos uma thread: a thread main. Alm dessa, e a mquina virtual mantm algumas outras que realizam tarefas como coleta de lixo a e ou nalizao de objetos. Algumas classes dispon ca veis na API de Java tambm utilizam e threads em suas implementaes. Como exemplo, podemos citar as classes de Java Swing co ou as classes da implementao de RMI. ca

1.3 Por que utilizar threads?


H muitos motivos para se usar threads. Entre eles, podemos citar: a Responsividade em Interfaces Grcas: imagine se o seu navegador web parasse a de carregar uma pgina s porque voc clicou no menu arquivo; a o e Sistemas multiprocessados: o uso de threads permite que o SO consiga dividir as tarefas entre todos os processadores dispon veis aumentando, assim, a ecincia do e processo; Simplicao na Modelagem de Aplicaes: suponha que voc precise fazer um ca co e programa que simule a interao entre diferentes entidades. Carros em uma esca trada, por exemplo. E mais fcil fazer um loop que atualiza todos os carros da a simulao ou criar um objeto carro que anda sempre que tiver espao a frente dele? ca c

Processamento ass ncrono ou em segundo plano: com threads um servidor de e-mail pode, por exemplo, atender a mais de um usurio simultaneamente. a

2 Utilizando threads
2.1 Criando threads
H duas formas equivalentes de criarmos uma thread em Java. Ou criamos um objeto a que estende a classe Thread e sobrescrevemos o seu mtodo public void run() ou e implementamos a interface Runnable que denida como: e package java.lang; public interface Runnable { public abstract void run(); } O exemplo abaixo ilustra a utilizao de threads utilizando as duas formas. ca public class Exemplo extends Thread { // classe interna que estende thread // (a classe definida como static apenas para podermos e // alocar uma inst^ncia no main) a static class Tarefa1 extends Thread { // mtodo que sobrescreve o mtodo run() herdado e e public void run() { for(int i=0; i<1000; i++) { System.out.println("Usando Herana"); c } } } // classe interna que implementa a interface Runnable static class Tarefa2 implements Runnable { public void run() { for(int i=0; i<1000; i++) { System.out.println("Usando Runnable"); } } } public static void main(String[] args) {

// basta criarmos uma int^ncia da classe que extende Thread a Thread threadComHeranca = new Tarefa1(); // primeiro devemos alocar uma inst^ncia de tarefa a Tarefa2 tarefa = new Tarefa2(); // e depois criamos a nova Thread, passando tarefa como argumento Thread threadComRunnable = new Thread(tarefa); // agora iniciamos as duas Threads threadComHeranca.start(); threadComRunnable.start(); } } A sa do texto, como esperado, so as duas mensagens intercaladas aleatoriamente da a na sa padro. Dependendo do sistema operacional e dos recursos computacionais do da a computador a sa pode parecer seqencial. Esse comportamento ser explicado na da u a seo 2.3. ca Apesar das duas formas de uso de threads serem equivalentes, classes que necessitem estender outra classe que no Thread so obrigadas a usar a interface Runnable, j que a a a Java no possui herana mltipla. Alm disso, os puristas em orientao a objetos a c u e ca dizem que normalmente estamos interessados em criar classes que usem threads e no a classes que sejam threads e que, portanto, dever amos implementar toda a lgica em uma o classe que implementa Runnable e depois criar a thread s quando for necessrio. o a

2.2 Poss veis estados de uma thread


Uma thread pode estar em um dos seguintes estados: criada, em execuo, ca suspensa ou morta. Uma thread se encontra no estado criada logo aps a instanciao de um objeto o ca Thread. Neste ponto nenhum recurso foi alocado para a thread. A unica transio ca vlida neste estado a transio para o estado em execuo. a e ca ca A thread passa para o estado em execuo quando o mtodo start() do objeto ca e e chamado. Neste ponto a thread pode car em execuo, se tornar suspensa ou se ca tornar morta. Na verdade, uma thread pode estar em execuo mas, ainda assim, no estar ativa. ca a Em computadores que possuem um unico processador imposs existirem duas threads e vel

executando ao mesmo tempo. Dessa forma uma thread que est esperando para ser a executada pode estar no estado em execuo e ainda assim estar parada. ca A thread se torna suspensa quando um destes eventos ocorrer: execuo do mtodo sleep(); ca e a thread chama o mtodo wait() para esperar que uma condio seja satisfeita; e ca a thread est bloqueada em uma operao de entrada/sa (I/O). a ca da A chamada ao comando sleep(int seconds) faz com que a thread espere um tempo determinado para executar novamente. A thread no executada durante este intervalo a e de tempo mesmo que o processador se torne dispon vel novamente. Aps o intervalo o dado, a thread volta ao estado em execuo novamente. ca Se a thread chamar o comando wait() ento ela deve esperar uma outra thread avisar a que a condio foi satisfeita atravs dos comandos notify() ou notifyAll(). Esses ca e mtodos sero explicados na seo 3. e a ca Se a thread estiver esperando uma operao de entrada/sa ela retornar ao estado ca da a em execuo assim que a operao for conclu ca ca da. Por m, a thread se torna morta se o mtodo run() chegar ao m de sua execuo e ca ou se uma exceo no for lanada e no for tratada por um bloco try/catch. ca a c a

2.3 Prioridades de threads


Anteriormente foi dito que diversas threads podem executar concorrentemente em um processo. Porm em sistemas monoprocessados isso parcialmente verdade. Conceitue e almente isso que ocorre. Mas na prtica apenas uma thread pode ocupar o processador e a por vez. A mquina virtual de Java dene uma ordem para que as threads ocupem a o processador de forma que o usurio tem a iluso de que elas realmente executam a a concorrentemente. A isso dado o nome de escalonamento de tarefas. e As prioridades de threads em Java podem variar entre os inteiros Thread.MIN PRIORITY e Thread.MAX PRIORITY. Quanto maior o inteiro, maior a prioridade. Para denir a prioridade de uma thread necessrio chamar o mtodo setPriority(int priority) da e a e classe Thread aps a instanciao do objeto. A mquina virtual utilizar sua prioridade o ca a a relativa para realizar o escalonamento. A mquina virtual sempre escolhe a thread com maior prioridade relativa e que esteja a no estado de execuo para ser executada. Uma thread de menor prioridade s ser ca o a executada se a thread atual for para o estado suspenso, se o mtodo run() terminar e ou se o mtodo yield(), cuja nalidade dar chance para que outras threads executem, e e for chamado. Se existirem duas threads de mesma prioridade esperando para serem executadas, a mquina virtual escolher uma delas, como em um escalonamento do tipo a a round-robin. A thread escolhida continuar ocupando o processador at que uma das seguintes a e condies se tornem verdadeiras: co uma thread de maior prioridade ca pronta para ser executada;

a thread atual chama yield() ou o mtodo run() termina; e o tempo reservado para a thread termina (s em SOs onde os processos possuem o um tempo mximo para permanecer no processador para serem executados. Disa cutiremos isso na seo 2.3.1). ca Como regra geral, podemos pensar que o escalonador escolher sempre a thread com a maior prioridade para ser executada em determinado momento. Porm, a mquina e a virtual livre para escolher uma thread de menor prioridade para evitar espera indenida e (starvation). 2.3.1 Fatias de tempo Alguns sistemas operacionais como o GNU/Linux ou o Microsoft r WindowsTM 1 fornecem para cada thread fatias de tempo (time-slices), que nada mais so do que o intervalo de a tempo mximo que a thread pode ocupar o processador antes de ser obrigada a ced-lo a e a outra thread. O exemplo da seo 2.1 produziria o seguinte resultado em sistemas que no possuem ca a fatias de tempo para suas threads: Usando Runnable (...) ("Usando Runnable" repetido 998 vezes) Usando Runnable Usando Herana c (...) ("Usando Herana" repetido 998 vezes) c Usando Herana c Ou seja, a primeira thread escalonada seria executada at o m. e J em um ambiente com fatias de tempo, as threads seriam interrompidas e o resultado a da execuo seria mais ou menos assim: ca Usando Usando Usando Usando Usando Usando (...) Runnable Runnable Herana c Runnable Herana c Herana c

A especicao de Java [2] no garante que o ambiente ter fatias de tempo. Fatias ca a a de tempo s ocorrero se o programa rodar em um sistema operacional que possua essa o a caracter stica. Seus programas no devem partir desse pressuposto. a

O autor no sabe ser imparcial quando o assunto sistema operacional. :-) a e

3 Sincronismo entre threads


A maior parte dos programas multithreadeds necessitam que as threads se comuniquem de forma a sincronizar suas aes. Para isso, a linguagem Java prov locks (tambm co e e chamados de monitores). Para impedir que mltiplas threads acessem um determinado recurso, cada thread u precisa adquirir um lock antes de usar o recurso e liber-lo depois. Imagine um lock a como uma permisso para usar o recurso. Apenas aquele que possuir a permisso poder a a a utilizar o recurso. Os outros devero esperar. A isso damos o nome de excluso mtua. a a u Threads usam locks em variveis compartilhadas para realizar sincronizaes e comua co nicaes entre threads. A thread que tiver o lock de um objeto sabe que nenhuma outra co thread conseguir obter o lock deste objeto. Mesmo se a thread que possuir o lock for a interrompida, nenhuma outra thread poder adquirir o lock at que a primeira volte a a e utilizar o processador, termine a tarefa e libere o lock. A thread que tentar obter um lock que j estiver com outra thread ser suspensa e s voltar ao estado em execuo a a o a ca quando obtiver novamente o lock. Como exemplo, suponha uma classe que encapsula o servio de impresso. Vrias thc a a reads de seu programa tentaro imprimir, mas apenas uma thread por vez deve conseguir. a Sua classe, ento, seria mais ou menos assim: a import java.io.File; class Impressora { synchronized public void imprimir(File arquivo) { (executa o cdigo para impress~o do arquivo) o a } public bolean estaImprimindo() { (devolve true se a impressora est imprimindo) a } } Note que no exemplo acima apenas uma thread por vez pode executar o mtodo e imprimir. Mas vrias threads podem vericar simultaneamente se a impressora est a a imprimindo algo ou no. a

3.1 Utilizando locks


Em Java, cada objeto possui um lock. Uma thread pode adquirir um lock de um objeto usando a palavra-chave synchronized. Esta palavra-chave pode ser usada na declarao ca de mtodos (neste caso o lock do objeto this utilizado) ou em blocos de cdigo (neste e e o caso, o objeto que contm o lock que ser usado deve ser especicado). Os seguintes usos e a de synchronized so equivalentes: a

synchronized public void teste() { faaAlgo(); c } public void teste() { synchronized(this) { faaAlgo(); c } } O lock liberado automaticamente assim que o bloco synchronized termina de ser e executado. Outra caracter stica importante sobre locks em Java que eles so reentrantes. Isso e a quer dizer que uma thread pode readquirir um lock que ela j possuir. Veja o exemplo a abaixo: public class Reentrante { public synchronized void entrar() { reentrar(); } private synchronized void reentrar() { System.out.println("N~o foi necessrio adquirir outro lock"); a a } } Ao executar o mtodo entrar() a thread deve adquir o lock do objeto this. Mas e graas ao fato dos locks serem reentrantes, no foi necessrio readquirir o lock para c a a executar o mtodo reentrar(). O lock s ser liberado quando o mtodo entrar() e o a e terminar se ser executado. Tambm tarefa do synchronized garantir a visibilidade das variveis compartilhae e a das. A mquina virtual de Java mantm uma memria cache para cada uma de suas a e o threads e uma memria principal que acessada por todas as threads. As atualizaes de o e co memria ocorrem primeiro no cache e s depois na memria principal. Para impedir que o o o uma thread atualize dados que so compartilhados apenas em seu cache local, a palavraa chave synchronized obriga que os dados sejam gravados na memria principal. Dessa o forma outras threads sempre acessam o valor mais atual dessas variveis compartilhadas. a

3.2 Variveis volteis a a


A palavra-chave volatile em Java serve para indicar que uma varivel voltil. Isso a e a signica trs coisas: primeiro que as operaes de leitura e escrita em variveis volteis e co a a so atmicas, isto , duas threads no podem escrever em uma varivel voltil ao mesmo a o e a a a

tempo. Segundo, as mudanas feitas em variveis volteis so automaticamente vis c a a a veis para todas as threads. E, por ultimo, indica para o compilador que a varivel pode ser a modicada por outras threads. Veja o seguinte cdigo: o class VolatileTeste { boolean flag; public void foo() { flag = false; if(flag) { (...) } } } Um compilador poderia otimizar o cdigo removendo todo o bloco dentro do if, pois o sabe que a condio do if nunca ser verdadeira. Porm isso no verdade em um ca a e a e ambiente multithreaded. Para impedir esse tipo de otimizao basta declarar a varivel ca a flag como sendo uma volatile boolean flag. Para uma explicao detalhada sobre o verdadeiro signicado das palavras-chave ca volatile e synchronized em termos do modelo de memria do Java, leia o excerto [7] o do livro Concurrent Programming in Java, de Doug Lea.

3.3 Quase todas as classes no so sincronizadas a a


Como sincronismo implica em uma pequena diminuio no desempenho do cdigo ca o (devido a obteno e liberao do lock), a maior parte das classes de uso geral, como as ca ca classes que estendem java.util.Collection, por exemplo, no so sincronizadas. Isso a a signica que classes como HashMap no podem ser usadas por mltiplas threads sem o a u uso de synchronized. Voc pode usar as classes que so Collection em uma aplicao multithreaded se e a ca voc utilizar um lock toda vez que voc acessar um mtodo em uma coleo compartie e e ca lhada entre as threads. Para cada coleo, voc deve usar o mesmo lock todas as vezes. ca e Normalmente utilizado o lock do prprio objeto que representa a coleo. e o ca A classe Collections prov um conjunto de wrappers para as interfaces List, Map e e Map. Voc pode sincronizar um Map com o mtodo Collections.synchronizedMap. e e Veja a API [1] da classe Collections para mais informaes. co Se a documentao da classe no diz nada sobre ser thread-safety, ento voc deve ca a a e assumir que a sincronizao responsabilidade sua. ca e

4 Os mtodos wait(), notify() e notifyAll() e


Cdigos como o do exemplo abaixo utilizam o que chamamos de espera ativa. Ou seja, o a thread ca esperando uma determinada condio ser satisfeita dentro de um lao. Essa ca c soluo ruim porque a thread ca ocupando o processador apenas para vericar se a ca e condio j foi satisfeita ou no. ca a a while(true) { if(condi~oEsperada) ca break; } A classe Object fornece os mtodos wait(), notify() e notifyAll(). Com esses e mtodos Java fornece uma implementao do conceito de monitores [10] que no utiliza e ca a espera ativa. Ao invs disso, esses mtodos enviam eventos para as threads indicando se e e elas devem ser suspensas ou se devem voltar ao estado em execuo. ca Para utilizar quaisquer dos trs mtodos de um objeto qualquer a thread deve possuir e e o lock do mesmo objeto. wait() faz a thread que chamou o mtodo dormir at que ela seja interrompida e e pelo mtodo Thread.interrupt(), at que o tempo especicado no argumento de e e wait() tenha passado ou at que outra thread a notique usando o mtodo notify() ou e e notifyAll(). Antes de ser suspensa, a thread libera o lock do objeto. Note, entretanto, que a thread continuar a manter todos os outros locks que ela possuir enquanto estiver a suspensa. O mtodo notify() acorda, se existir, alguma2 thread que esteja esperando um evento e deste objeto (ou seja, que tenha executado o comando wait() neste objeto). E o comando notifyAll() acorda todas as threads que estejam esperando neste objeto. Antes que a thread acordada possa prosseguir, ela deve esperar a thread que a acordou liberar o lock e obt-lo novamente. e O exemplo abaixo deve tornar esses conceitos mais claros. Ele implementa uma caixa onde um inteiro pode ser armazenado. Se uma thread tentar armazenar um inteiro em uma caixa cheia ela ser suspensa at que alguma thread retire o inteiro da caixa. a e Do mesmo modo, se uma thread tentar retirar um inteiro de uma caixa vazia ela ser a suspensa at que uma outra thread coloque um inteiro na caixa. Esse um exemplo do e e problema do produtor/consumidor. public class Caixa { private int conteudo; private boolean cheia = false; public synchronized int get() { while (cheia == false) {
2

Uma thread qualquer acordada. No h nenhuma garantia de ordem. e a a

10

try { // espera at que um produtor coloque um valor. e wait(); } catch (InterruptedException e) { } } cheia = false; // informa ` todos que a caixa foi esvaziada. a notifyAll(); return conteudo; } public synchronized void put(int valor) { while (cheia == true) { try { // espera at que um consumidor esvazie a caixa. e wait(); } catch (InterruptedException e) {} } conteudo = valor; cheia = true; // informa ` todos que um novo valor foi inserido. a notifyAll(); } }

5 Mais Informaes co
Informaes mais detalhadas sobre programao multithreaded podem ser encontradas co ca nas referncias deste texto. e A API [1] da classe Thread de Java traz, alm de seus mtodos e atributos, uma e e pequena explicao sobre o seu funcionamento. A API da classe Object traz explicaes ca co sobre os mtodos wait(), notify() e notifyAll() necessrios para a manipulao de e a ca locks. O cap tulo 17 (Threads and Locks) da especicao da linguagem Java [2] possui inca formaes detalhad co ssimas sobre o funcionamento de threads, locks e sobre a atomicidade de operaes em Java que, apesar de irem muito alm do escopo deste texto, valem a co e leitura. Recomendo tambm a leitura dos artigos [6] e [8], que discutem os problemas da e tcnica de double check, muito utilizada em programao concorrente mas que no e ca a funciona em Java devido a especicao do modelo de memria de Java. ca o

11

Referncias e
[1] JavaTM 2 Plataform API Specication. http://java.sun.com/j2se/1.4.2/docs/ api/. [2] James Gosling, Bill Joy, Gilad Bracha e Guy Steele. The Java Language Specication, 2a edio. Addison-Wesley, 2000. Dispon em http://java.sun.com/docs/books/ ca vel jls/. [3] Mary Campione e Kathy Walrath. The Java Tutorial, terceira edio. Addisonca Wesley, 1998. Dispon em http://java.sun.com/docs/books/tutorial/. vel [4] Brian Goetz. Introduction to Java threads. IBM DeveloperWorks, setembro 2002. Dispon vel em http://www-106.ibm.com/developerworks/edu/ j-dw-javathread-i.html. [5] Alex Roetter. Writing multithreaded Java applications. IBM DeveloperWorks, fevereiro 2001. Dispon vel em http://www-106.ibm.com/developerworks/library/ j-thread.html. [6] Brian Goetz. Double-checked locking: Clever, but broken. JavaWorld, fevereiro 2001. Dispon em http://www.javaworld.com/jw-02-2001/jw-0209-double.html. vel [7] Doug Lea. Synchronization and the Java Memory Model. Dispon vel em http:// gee.cs.oswego.edu/dl/cpj/jmm.html. [8] David Bacon et al. The Double-Checked Locking is BrokenDeclaration. Dispon vel em http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking. html. [9] Neel Kumar. IBM DeveloperWorks, abril 2000. Dispon em http://www-106.ibm. vel com/developerworks/java/library/j-threadsafe/. [10] Gregory Andrews. Foundations of Multithreaded, Parallel, and Distributed Programming. Addison-Wesley, 1999.

12