Coleções, Genéricos, Threads Marco Antonio Collection • Principais métodos da interface Collection. Map • Principais métodos da interface Map. Hierarquia de Collecion Ordenação • Uma classe é ordenada se pode ser iterada pelos seus elementos em uma ordem específica, através de um índice ou por exemplo pela ordem de inserção. Classificação • Uma classe classificada, quando seus elementos estão classificados por algum critério, como por exemplo, em ordem alfabética, crescente ou cronológica etc. Toda classe classificada é ordenada, já uma classe ordenada pode não ser classificada. List • As classes que implementam a interface List, relevam o índice, com isso podemos inserir um item no meio de uma lista. • As classes que implementam a interface List são ordenadas por meio de um índice, isso permite o acesso a um elemento que se encontra no meio da lista, através de seu índice. • É uma espécie de sequência para armazenamento de objetos. List • O que precisamos saber é que o índice em uma lista é relevante, toda lista é ordenada, ou seja, podemos iterar em uma ordem especifica, seja ela pela ordem de inserção ou pela ordem do índice. • Lista não é classificada. Características das implementações Raw types • Quando um tipo genérico (como uma coleção) é usado sem o parâmetro, chamamos de raw types. • A utilização de raw types não é recomendada, mas é essencial para manter compatibilidade com código legado. • Em função disso aparece ao lado da coleção um warning indicando que você não está utilizando o parâmetro de tipo. • Para evitar esse warning, utilize a sintaxe logo abaixo. @SuppressWarnings("unchecked") public class TesteColecaoSemGenericos { {código da classe} } Coleções sem genéricos • Apesar de nossa coleção ter apenas um tipo de classe, para recuperarmos os objetos precisamos fazer um cast. • Funciona perfeitamente, mas ainda pode ser melhorado. Finalmente, coleções com genéricos • Com essa sintaxe, não precisamos mais fazer cast. – Collection<Pessoa> lista = new ArrayList<Pessoa>(); • Uma consequência direta da aplicação de genéricos na nossa coleção é que restringimos a adição de objetos somente do tipo Pessoa. TesteColecaoComGenericos package com.javabasico.genericos; import java.util.*; public class TesteColecaoComGenericos { public static void main(String[] args) { Pessoa p1 = new Pessoa(); p1.setNome("Marco"); Pessoa p2 = new Pessoa(); p2.setNome("Diego"); 1. Collection<Pessoa> lista = new ArrayList<Pessoa>(); lista.add(p1); lista.add(p2); 2. Iterator<Pessoa> ite = lista.iterator(); 3. while (ite.hasNext()) { 4. Pessoa pessoa = ite.next(); } } } Tipos parametrizados 1. 2. 3. 4. Declaração do tipo parametrizado da coleção. Declaração do tipo parametrizado do iterator. Loop normal (while). Não precisa mais de conversão, o compilador já sabe que o objeto é do tipo Pessoa. Nem mesmo aceitaria outro tipo de classe. TesteColecaoComGenericos package com.javabasico.genericos; import java.util.*; public class TesteColecaoComGenericos { public static void main(String[] args) { Pessoa p1 = new Pessoa(); p1.setNome("Marco"); Pessoa p2 = new Pessoa(); p2.setNome("Diego"); Collection<Pessoa> lista = new ArrayList<Pessoa>(); lista.add(p1); lista.add(p2); 1. for(Pessoa p : lista){ 2. System.out.println(p.getNome()); } } } foreach • • 1. 2. Melhoramento do for tradicional. Não é mais necessário utilizar o iterator. Para cada Pessoa dentro da lista... Imprime o nome. CaixaDeObjeto sem genéricos • Vamos dar uma olhada em um exemplo de classe que utiliza Object, ou seja, não utiliza tipos parametrizados. CaixaDeObjeto package com.javabasico.genericos; public class CaixaDeObjeto { private Object objeto; public void adiciona(Object objetoAdicionado) { objeto = objetoAdicionado; } public Object recuperaObjeto() { return objeto; } } Conversão obrigatória • Sem a utilização de genéricos precisamos sempre fazer conversões. – Integer valorRecuperado = (Integer) caixa.recuperaObjeto(); • Não existe validação nenhuma em tempo de compilação já que a conversão é forçada. TesteCaixaDeObjeto package com.javabasico.genericos; public class TesteCaixaDeObjeto { public static void main(String[] args) { CaixaDeObjeto caixa = new CaixaDeObjeto(); caixa.adiciona(new Integer(10)); Integer valorRecuperado = (Integer) caixa.recuperaObjeto(); System.out.println("Valor integer: " + valorRecuperado); } } Erro de conversão • Nada impede o programador de converter para um tipo errado. • Infelizmente esse problema só será descoberto em tempo de execução. TesteCaixaDeObjeto package com.javabasico.genericos; public class TesteCaixaDeObjeto { public static void main(String[] args) { CaixaDeObjeto caixa = new CaixaDeObjeto(); caixa.adiciona(new Integer(10)); String valorRecuperado = (String) caixa.recuperaObjeto(); System.out.println("Valor integer: " + valorRecuperado); } } Mensagem de erro • A exceção lançada quando a conversão não pode ser feita é essa a seguir. Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.javabasico.genericos.TesteCaixaDeObjeto.main(TesteCaixaDeObjeto.java:7) Tipo parametrizado • Vamos atualizar nossa caixa de objetos com um tipo parametrizado. • Esse tipo (T) será informado quando criarmos um objeto do tipo CaixaDeObjeto, como uma variável. • A mesma técnica pode ser aplicada a interfaces ou métodos. CaixaDeObjeto package com.javabasico.genericos; 1. public class CaixaDeObjeto<T> { 2. private T objeto; 3. public void adiciona(T objetoAdicionado) { objeto = objetoAdicionado; } 4. public T recuperaObjeto() { return objeto; } } CaixaDeObjeto • • • • Declaração do tipo parametrizado. A variável objeto agora é do tipo parametrizado. Esse tipo será indicado mais adiante. O método adiciona só permite que você utilize o tipo T. Recupera o objeto convertido para o tipo T. Testando • Quando criamos um objeto do tipo CaixaDeObjeto devemos informar o tipo, conforme a sintaxe. • A partir desse momento, não será mais necessário fazer cast. TesteCaixaDeObjeto package com.javabasico.genericos; public class TesteCaixaDeObjeto { public static void main(String[] args) { 1. CaixaDeObjeto<Integer> caixa = new CaixaDeObjeto<Integer>(); caixa.adiciona(new Integer(10)); System.out.println("Valor integer: " + caixa.recuperaObjeto()); } } TesteCaixaDeObjeto 1. Agora parametrizamos a caixa de objetos. Essa caixa de objetos só aceitará objetos do tipo Integer. • Qualquer outro tipo de dado será considerado como erro pelo compilador. Outros tipos de objetos • Vamos criar mais uma caixa de objetos parametrizada para o tipo Pessoa. TesteCaixaDeObjeto package com.javabasico.genericos; public class TesteCaixaDeObjeto { public static void main(String[] args) { CaixaDeObjeto<Integer> caixa = new CaixaDeObjeto<Integer>(); caixa.adiciona(new Integer(10)); System.out.println("Valor integer: " + caixa.recuperaObjeto()); CaixaDeObjeto<Pessoa> caixaPessoa = new CaixaDeObjeto<Pessoa>(); Pessoa pessoa = new Pessoa(); pessoa.setNome("Marco"); caixaPessoa.adiciona(pessoa); System.out.println("Nome: " + caixaPessoa.recuperaObjeto().getNome()); } } Nomenclatura (apenas sugerida) • E - Elemento (usado extensivamente no Java Collections Framework) • K - Key • N - Number • T – Type (Classe) • V - Value • S,U,V etc. - 2nd, 3rd, 4th types Mais tipos parametrizados • Com o próximo exemplo você pode ver que métodos também aceitam tipos parametrizados. CaixaDeObjeto package com.javabasico.genericos; public class CaixaDeObjeto<T> { private T objeto; public void adiciona(T objetoAdicionado) { objeto = objetoAdicionado; } public T recuperaObjeto() { return objeto; } public <U> void verifica(U u) { System.out.println("T: " + objeto.getClass().getName()); System.out.println("U: " + u.getClass().getName()); } } TesteCaixaDeObjetoInspecao package com.javabasico.genericos; public class TesteCaixaDeObjetoInspecao { public static void main(String[] args) { CaixaDeObjeto<Integer> caixa = new CaixaDeObjeto<Integer>(); caixa.adiciona(new Integer(10)); caixa.verifica(new Pessoa()); caixa.verifica(new String("Alguma frase")); } } Memory import java.util.Date; public class Memory { public static void main(String[] args) { Runtime rt = Runtime.getRuntime(); System.out.println("Memoria total: " + rt.totalMemory()); System.out.println("Memoria Antes: " + rt.freeMemory()); Date d = null; String s = null; for (int i = 0; i < 50000; i++) { d = new Date(); d = null; s = "TEsdfafsadfjasdkfajdçfas"; s = null; } System.out.println("Memoria Depois: " + rt.freeMemory()); rt.gc(); System.out.println("Memoria Final: " + rt.freeMemory()); } } Threads • Também chamados de segmentos. Execucao public class Execucao { public static void main(String[] args) { Proc p = new Proc(); Thread t = new Thread(p); t.start(); while (true) { System.out.println(Thread.currentThread().getName() + " executando"); } } } class Proc implements Runnable { public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " executando"); } } } TesteThread public class TesteThread { public static void main(String[] args) { Counter ct = new Counter(); ct.start(); System.out.println("The thread has been started"); } } class Counter extends Thread { public void run() { for (int i = 1; i <= 50; i++) { System.out.println("Count: " + i); } } } TesteThread public class TesteThread { public static void main(String[] args) { Counter ct = new Counter(); ct.run(); System.out.println("The thread has been started"); } } class Counter extends Thread { public void run() { for (int i = 1; i <= 5; i++) { System.out.println("Count: " + i); } } } MultiplasThreads public class MultiplasThreads { public static void main(String[] args) { System.out.println("The main thread of execution started"); RunCounter1 rct1 = new RunCounter1("First Thread"); RunCounter1 rct2 = new RunCounter1("Second Thread"); RunCounter1 rct3 = new RunCounter1("Third Thread"); } } class RunCounter1 implements Runnable { Thread myThread; RunCounter1(String name) { myThread = new Thread(this, name); myThread.start(); } public void run() { for (int i = 1; i <= 5; i++) { System.out.println("Thread: " + myThread.getName() + " Count: " + i); } } } O método start • Quando o método start é chamado a thread não roda imediatamente. • Ela vai para o scheduler. Estados • • • • Novo - estado que uma thread fica no momento de sua instanciação, antes da chamada do método start(); Executável - estado em que a thread fica disponível para ser executada e no aguardo do escalonador de thread, esperando a sua vez de se executar; Execução - Momento em que a thread está executando, está operando; Espera/Bloqueio/Suspensão - esse estado pode ser dar por inúmeros motivos. – • Uma thread em sua execução pode se bloquear porque algum recurso ou objeto não está disponível, por isso seu estado pode ficar bloqueado, até que esse recurso/objeto esteja disponível novamente assim seu estado torna-se executável, ou então, uma thread pode ficar suspensa porque o programador definiu um tempo de espera, assim que esse tempo expirar essa thread volta ao estado executável para continuar seus serviços; Inativo - a partir do momento em que o método run() foi concluído, a thread se tornará inativa, porém ainda existirá o objeto na memória, somente não como uma linha de execução, e não poderá novamente ser iniciada, ou seja, qualquer tentativa de chamada do método start() após a conclusão do métodos run(), uma exceção será lançada; TesteRunnable public class TesteRunnable { public static void main(String[] args) { RunCounter rct = new RunCounter(); Thread th = new Thread(rct); th.start(); System.out.println("Thread já iniciada"); th.start(); } } Métodos • • • • • • • run() - é o código que a thread executará. start() - sinaliza à JVM que a thread pode ser executada, mas saiba que essa execução não é garantida quando esse método é chamado, e isso pode depender da JVM. isAlive() - volta true se a thread está sendo executada e ainda não terminou. sleep() - suspende a execução da thread por um tempo determinado; yield() - torna o estado de uma thread executável para que thread com prioridades equivalentes possam ser processadas; currentThread() - é um método estático da classe Thread que volta qual a thread que está sendo executada. getName() - volta o nome da Thread, você pode especificar o nome de uma Thread com o método setName() ou na construção da mesma, pois existe os construtores sobrecarregados. ExemploContador public class ExemploContador { public static void main(String args[]) { ContadorThread c1 = new ContadorThread(); c1.setQtde(10); c1.setName("t001"); c1.start(); ContadorThread c2 = new ContadorThread(); c2.setQtde(15); c2.setName("t002"); c2.start(); } } Cont... class ContadorThread extends Thread { private int qtde = 0; public void run() { for (int i = 0; i <= 100; i++) { if ((i % qtde) == 0) { System.out.println(Thread.currentThread().getName() + "> " + i); } try { sleep(50); } catch (InterruptedException ex) { } } } public void setQtde(int value) { this.qtde = value; if (this.qtde == 0) this.qtde = 10; } } yield • Uma chamada a yield (Thread.yield()) coloca a thread de volta no estado de executável, permitindo que outras threads com a mesma prioridade (ou maior) possam executar. • Esse método se propõe a isso. Mas não garante que o comportamento ocorra. • Somente pára a execução se outra estiver pronta para executar. sleep • Coloca a thread em espera. Após o período de sleep acabar, a thread volta para o estado de executável: não entra imediamente em execução. • Pára a execução em todos os casos. wait • Usado em blocos sincronizados. • Pára a execução até ser notificado por outra thread para retornar. notify • Acorda uma thread que está na fila do escalonador. • Se essa thread estiver pronta muda o seu estado para executável. • notifyAll() acorda todas as threads em estado de aguardando. • wait(), notify() e notifyAll() devem ser usados em blocos sincronizados e são implementados em Object. ClienteSocket import java.io.*; import java.net.*; public class ClienteSocket { public void leDados() { try { Socket clientSock = new Socket("195.45.3.4", 11000); InputStream in = clientSock.getInputStream(); int len = 0; BufferedOutputStream outFile = new BufferedOutputStream( new FileOutputStream("response.txt")); byte buf[] = new byte[256]; while ((len = in.read(buf)) != -1) { outFile.write(buf, 0, len); } } catch (IOException e) { e.printStackTrace(); } } } Block • A thread está bloqueada no método read. • Operações de IO colocar a thread naturalmente em block. Questões • Quais métodos são usados para executar comunicação entre threads? A. yield() B. sleep(…) C. notify() D. wait() Questões • Quais desses métodos estão definidos em Object? A. yield() B. sleep(…) C. run() D. wait() E. notify() QuestaoThread public class QuestaoThread { public static void main(String[] args) { CounterT ct = new CounterT(); ct.start(); System.out.println("The thread has been started"); } } class CounterT extends Thread { protected void run() { System.out.println("Hello"); } } Quais a saída? • The thread has been started. / Hello • Hello / The thread has been started. • Qualquer das anteriores • Erro na linha 9 Questões • Quais declarações são verdadeiras sobre o método wait()? A. Uma thread chama wait() para parar temporariamente a execução a execução de outras threads. B. Quando uma thread executa wait(), ela pára sua execução temporariamente. C. Uma chamada a wait() pára a execução da aplicação. D. wait() pertence à classe Object. E. wait() pertence à classe Thread. Veja o código public class ThreadOrder { static int count = 0; public static void main(String[] args) { CounterO ct = new CounterO(); TrackerO trk1 = new TrackerO(ct, "thread one"); TrackerO trk2 = new TrackerO(ct, "thread two"); trk1.start(); trk2.start(); } } class TrackerO extends Thread { CounterO ct; String message; TrackerO(CounterO ct, String msg) { this.ct = ct; message = msg; } public void run() { System.out.println(message); } } class CounterO { private int count = 0; public int nextCounter() { synchronized (this) { count++; return count; } } } Questão • thread one / thread two • thread two / thread one • Às vezes a primeira alternativa, outras a segunda. • Exceção na linha 8. Questão • O que acontece quando uma thread tem o seguinte bloco de código em seu método run? – sleep(500); 1. A execução pára e reinicia 500ms depois. 2. A execução pára e reinicia não antes que 500ms. 3. Erro de compilação. Você não pode chamar o método sleep. 4. Erro de compilação porque sleep não permite argumentos. notify • Uma thread thr está aguardando entre outras para ser executada. Como você pode usar notify() para tirar thr do estado de aguardando? • Execute thr.notify() de um bloco de código sincronizado. • Execute notify(thr) de um bloco de código sincronizado. • Com notify você não pode especificar qual thread seria retirada do estado de aguardando. Questões • Quais desses métodos garantem que uma thread será colocada fora do estado de executando? A. wait() B. yield() C. sleep(500) D. kill() E. notify() Questão • Quais dos seguintes são construtores válidos para Thread()? A. Thread() B. Thread(int millisec) C. Thread(Runnable r) D. Thread(Runnable r, String name) E. Thread(int priority) Questões • Quais desses métodos são definidos na classe Thread? A. yield() B. sleep(…) C. run() D. wait() E. notify() Exemplos • Situações um pouco mais complexas. TesteProduto public class TesteProduto { public static void main(String[] args) { Produto p = new Produto(5); Thread[] t = new Thread[15]; for (int i = 0; i < t.length; i++) { t[i] = new Thread(p); t[i].setName("Cliente: " + i); t[i].start(); } } } public class Produto implements Runnable { private int estoque = 5; public void run() { try { for (int i = 0; i < 2; i++) { efetuarPedido(); } } catch (Exception ex) { } } Cont... public void efetuarPedido() { try { if (this.estoque > 0) { System.out.println("Pedido faturado para o cliente " + Thread.currentThread().getName()); Thread.sleep(250); this.estoque--; } else { System.out.println("Não tem estoque para o cliente " + Thread.currentThread().getName()); } } catch (Exception ex) { } } public Produto(int value) { this.estoque = value; } } Sincronização • Mude o método efetuarPedido para: – public synchronized efetuarPedido InteraThread import java.io.*; public class InteraThread { public static void main(String[] args) { try { Arquivo arq = new Arquivo(new File("saida.txt")); Thread[] a = new Thread[2]; for (int i=0; i < a.length; i++) { a[i] = new Thread(new Leitura(arq)); a[i].setName( ""+i); a[i].start(); } Thread b = new Thread( new Gravacao(arq) ); b.start(); b.join(); System.out.println("Processo finalizado..."); } catch (Exception ex) {} } } Gravacao class Gravacao implements Runnable { Arquivo arq; public Gravacao(Arquivo value) { arq = value; } public void run() { arq.gravar(); } } class Leitura implements Runnable { Arquivo arq; public Leitura(Arquivo value) { arq = value; } public void run() { arq.ler(); } } Arquivo class Arquivo { File file; public Arquivo(File value) { file = value; } synchronized public void ler() { try { if (!file.exists()){ wait(); } System.out.print( "thread# "+Thread.currentThread().getName() + ">>> "); if (file.exists()){ FileInputStream fis = new FileInputStream(file); int in; while ((in=fis.read())!=-1) { System.out.print((char)in); } fis.close(); } } catch (Exception e) { e.printStackTrace(); } } Cont... synchronized public void gravar() { try { if (!file.exists()){ FileOutputStream fos = new FileOutputStream(file); for (int i=0; i < 5; i++) { fos.write( ("linha "+i).getBytes() ); } fos.write("\n".getBytes()); fos.close(); System.out.print("Entrou no notify"); notify(); notity(); //ou notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } } • O código dispara 10 threads para leitura de um arquivo que nem foi criado. • Dessa forma, todo mundo fica esperando até que esse arquivo seja criado. • Uma vez criado o arquivo, a threads saem do estado wait e podem ler do disco. TesteDeadLock public class TesteDeadLock { public static void main(String[] args) { Object obj1 = "objectA"; Object obj2 = "objectB"; DeadLock t1 = new DeadLock(obj1, obj2); DeadLock t2 = new DeadLock(obj2, obj1); t1.start(); t2.start(); System.out.println("Todas as threads foram iniciadas"); } } DeadLock class DeadLock extends Thread { private Object resourceA; private Object resourceB; public DeadLock(Object a, Object b) { resourceA = a; resourceB = b; } Cont... public void run() { while (true) { System.out.println("A thread " + Thread.currentThread().getName() + " está esperando pelo lock de " + resourceA); synchronized (resourceA) { System.out.println("A thread " + Thread.currentThread().getName() + " recebeu o lock de " + resourceA); System.out.println("A thread " + Thread.currentThread().getName() + " está esperando pelo lock de " + resourceB); synchronized (resourceB) { System.out.println("A thread " + Thread.currentThread().getName() + " recebeu o lock de " + resourceB); try { Thread.sleep(500); } catch (Exception e) { } } } } } } Dead Lock • Dois ou mais processos estão esperando por um evento. • O problema: os dois processos estão em estado de waiting (aguardando).