Temp - Java Programmer - Modulo III (Online) PDF
Temp - Java Programmer - Modulo III (Online) PDF
Cód.: TE 1727/0_EAD
Créditos
2
Todos os direitos autorais reservados. Este material de estudo (textos, imagens, áudios e vídeos) não pode
ser copiado, reproduzido, traduzido, baixado ou convertido em qualquer outra forma eletrônica, digital
ou impressa, ou legível por qualquer meio, em parte ou no todo, sem a aprovação prévia, por escrito, da
Impacta Participações e Empreendimentos Ltda., estando o contrafator sujeito a responder por crime de
Violação de Direito Autoral, conforme o art.184 do Código Penal Brasileiro, além de responder por Perdas
e Danos. Todos os logotipos e marcas utilizados neste material pertencem às suas respectivas empresas.
“As marcas registradas e os nomes comerciais citados nesta obra, mesmo que não sejam assim identificados,
pertencem aos seus respectivos proprietários nos termos das leis, convenções e diretrizes nacionais e internacionais.”
Coordenação Geral
Marcia M. Rosa Roteirização
Sandro Luiz de Souza Vieira
Coordenação Editorial
Henrique Thomaz Bruscagin Aula ministrada por
Sandro Luiz de Souza Vieira
Supervisão de Desenvolvimento Digital
Alexandre Hideki Chicaoka Edição e Revisão final
Luiz Fernando Oliveira
Produção, Gravação, Edição de Vídeo e Finalização
Bruno Michel Vasconcellos de Andrade Diagramação
(Impacta Produtora) Bruno de Oliveira Santos
Luiz Felipe da Silva Porto (Impacta Produtora)
Xandros Luiz de Oliveira Almeida (Impacta Produtora)
Edição nº 1 | 1727/0_EAD
junho/2015
Este material é uma nova obra derivada da seguinte obra original, produzida por
TechnoEdition Editora Ltda., em Set/2014: Java Programmer
Autoria: Sandro Luiz de Souza Vieira
Apresentação....................................................................................................................................................... 06
2. Asserções .......................................................................................................................................... 39
2.1. Introdução......................................................................................................................40
2.2. Sintaxe das asserções.....................................................................................................40
2.3. Ativando e desativando asserções...................................................................................41
2.4. Switches de linha de comando........................................................................................42
2.5. Regras para o uso de asserções......................................................................................43
2.6. Classe AssertionError......................................................................................................46
2.6.1. Construtores...................................................................................................................46
Teste seus conhecimentos............................................................................................................................ 49
3. Threads................................................................................................................................................ 53
3.1. Introdução......................................................................................................................54
3.2. Programação multithreaded............................................................................................55
3.3. Implementando multithreading.......................................................................................57
3.3.1. java.lang.Thread.............................................................................................................58
3.3.2. java.lang.Runnable..........................................................................................................60
3.4. Construtores...................................................................................................................61
3.5. Estados da thread...........................................................................................................64
3.6. Scheduler........................................................................................................................66
3.7. Prioridades das threads...................................................................................................67
3.7.1. Método yield().................................................................................................................69
3.7.2. Método join()..................................................................................................................70
3.7.3. Método isAlive()..............................................................................................................70
3.7.4. Método sleep()................................................................................................................70
3.8. Sincronização..................................................................................................................72
3.8.1. Palavra-chave synchronized.............................................................................................72
Java Programmer – Módulo III (online)
4
4. JDBC...................................................................................................................................................... 87
4.1. Introdução......................................................................................................................88
4.2. Pacote java.sql................................................................................................................89
4.3. Abrindo e fechando conexões.........................................................................................89
4.3.1. Carregando drivers.........................................................................................................89
4.3.2. Abrindo a conexão..........................................................................................................91
4.3.3. Interface Connection.......................................................................................................92
4.3.4. Classe DriverManager.....................................................................................................93
4.3.5. Estabelecendo a conexão com o banco de dados............................................................93
4.3.6. Método Close..................................................................................................................96
4.4. Operações na base de dados...........................................................................................97
4.5. Operações parametrizadas..............................................................................................99
4.6. Transações.....................................................................................................................101
4.7. Consultas........................................................................................................................102
4.8. Pacote javax.sql..............................................................................................................104
4.8.1. DataSource.....................................................................................................................105
4.8.2. Pool de conexões e instruções........................................................................................106
4.8.3. ConnectionPoolDataSource.............................................................................................107
4.8.3.1. PooledConnection...........................................................................................................109
4.8.4. Transações distribuídas..................................................................................................112
4.8.5. RowSet............................................................................................................................112
Teste seus conhecimentos............................................................................................................................ 119
Bem-vindo!
É um prazer tê-lo como aluno do nosso curso online de Java Programmer – Módulo III. Este é o
curso ideal para você que já domina os assuntos essenciais da linguagem Java e agora busca
conhecer recursos adicionais muito utilizados nela.
No decorrer das aulas, você conhecerá threads, asserções, comandos de entrada e saída de
informações e realizará conexões com bases de dados utilizando o Java Database Connectivity.
Por fim, verá como a linguagem realiza o gerenciamento automático de memória por meio do
recurso Garbage Collector.
Para ter um bom aproveitamento deste curso, é imprescindível que você tenha participado
do segundo módulo do curso Java Programmer, ou possua conhecimentos equivalentes. Bom
aprendizado!
Como estudar?
Material de Apoio!
Videoaulas sobre os assuntos que você precisa saber no terceiro módulo de Java
Programmer.
Parte teórica, com mais exemplos e detalhes para você que quer se aprofundar no
assunto da videoaula.
1.1. I/O
A linguagem Java trabalha com um fluxo controlável de entrada e saída de informações, chamado
stream, que também é definido como fluxo Input/Output (entrada/saída), ou simplesmente
fluxo I/O. Além disso, o stream é manipulado por objetos e classes da linguagem Java. Nos
tópicos a seguir, serão apresentadas as classes que representam os streams de saída e entrada,
assim como seus respectivos métodos. Você também conhecerá as classes usadas para a
leitura de arquivos binários e de texto e o uso de paths.
1.1.1. Classe OutputStream
Um stream de saída tem a capacidade de enviar bytes para um coletor (sink). Esse stream
é representado por diversas classes, que, por sua vez, possuem uma superclasse chamada
OutputStream.
Depois de compilado e executado o código anterior, um arquivo do tipo .txt será gerado com
o nome GerarArquivo, como mostra a imagem a seguir:
•• write(...): Grava bytes de saída para o destino. Possui algumas versões sobrecarregadas;
•• flush(): Gera a saída de um byte de gravação pendente para o stream de destino. O byte
de gravação pendente pode estar em um buffer interno;
•• close(): Fecha o stream de saída. Em vista disso, os recursos de sistema que estavam
sendo usados por esse stream são liberados para outros fins.
Uma condição para que as aplicações sejam capazes de definir uma subclasse de OutputStream
é oferecer pelo menos um método de gravação de byte de saída.
1.1.1.1. Métodos
•• write (int b)
Tendo como parâmetro o argumento b (o byte), o método write (int b), cuja implementação
se faz necessária nas subclasses de OutputStream, grava o byte especificado para o stream
de saída.
De acordo com o contrato geral de gravação, o stream de saída tem um byte escrito de 8 bits
de ordem inferior do argumento b. Já o byte de ordem superior, 24 bits, é desconsiderado.
•• write (byte[] b)
Este método da classe OutputStream grava bytes de tamanho b.length para o stream de
saída, a partir do array do byte especificado.
O método usa o parâmetro b (dados) e, caso ocorra um erro de entrada e saída, ele lança
uma IOException. Pelo contrato geral de gravação (b), esta deve produzir o mesmo efeito da
chamada write (b, 0, b.length).
Este método possui os parâmetros b (dados), off (offset de início nos dados) e len (quantidade
de bytes a ser gravada). Além disso, a partir do array de byte especificado, grava bytes len,
com início no offset off do stream de saída.
Segundo o contrato geral de gravação (b, off, len), alguns bytes do array b devem ser escritos
para o stream de saída nesta sequência:
•• O último é o b[off+len-1].
Ressaltamos que as subclasses são estimuladas a substituir write(b, off, len) por outro método,
e colocar à disposição uma implementação que obtenha resultados mais satisfatórios.
•• flush()
De acordo com o contrato geral do método flush(), se os bytes foram anteriormente escritos
e armazenados em buffer pela implementação do stream de saída, eles devem ser escritos
imediatamente para seu destino.
Java Programmer – Módulo III (online)
12
•• close()
O método close() fecha o stream de saída, como determina o contrato geral. Com o stream
fechado, os recursos de sistema por ele utilizados são liberados para outros fins.
Lembre-se que, depois de fechado pelo método close(), o stream não poderá ser reaberto,
tornando-se incapacitado para executar saídas.
1.1.2. Classe InputStream
As classes que representam um stream de entrada de bytes têm como superclasse a
InputStream, cuja sintaxe é a seguinte:
Se sua intenção é fazer com que uma aplicação defina uma subclasse de InputStream, é
necessária a utilização de um método que retorne o próximo byte de entrada.
1.1.2.1. Métodos
•• read()
O método read(), cuja implementação deve ser oferecida por uma subclasse, faz a leitura do
próximo byte de dados que chega do stream de entrada, sendo que o byte de valor retornado
apresentará um valor int entre 0 e 255.
Se não houver um único byte de dados em razão de o stream ter chegado ao seu final, o
método read() retorna o valor –1.
Arquivos – I/O e NIO
13
•• read(byte[] b)
A partir do stream de entrada, este método lê alguns números de bytes (inteiros) que são, em
seguida, depositados no array b do buffer.
•• Se não houver bytes disponíveis em razão de o stream ter atingido seu final, o método
retorna o valor –1. Na existência de pelo menos um byte, este é lido pelo método e
alocado em b;
•• O número de bytes lidos deve ser o k. No máximo, esse número pode ser igual à
extensão de b. Assim, os referidos bytes serão armazenados nos elementos b[0] até
b[k-1], de maneira que os elementos b[k] até b[b.length-1] não sofrerão alterações.
Java Programmer – Módulo III (online)
14
Este método basicamente lê bytes de len, do stream de entrada para um array de bytes, e é
dado pela seguinte sintaxe:
Este método, cujos parâmetros são b, off e len, chama o método read() várias vezes seguidas.
A seguir, você pode ter uma ideia de como isso acontece:
•• Com o arquivo stream finalizado, os bytes lidos até o momento em que ocorreu a
exceção são guardados em b e os bytes lidos antes à exceção retornam.
O b representa o buffer para qual os dados são lidos. O off representa o offset de início de
uma array b em que os dados são escritos. Já o parâmetro len é a quantidade de bytes que
serão lidos.
•• Normalmente, read(b, off, len) retorna o número total de bytes lidos para o buffer;
•• Se len ou off apresentarem valores negativos, ou se a soma de off e len tiver como
total um valor maior do que a extensão do array b, o método read(b, off, len) lança
uma exceção IndexOutOfBoundsException;
•• Tenta fazer a leitura de pelo menos um byte. Às vezes, len apresenta o valor 0, o que
significa que nenhum byte foi lido. Neste caso, o método retorna 0 porque a fonte de
dados da leitura está vazia. Quando o método chega ao fim do arquivo e não há mais
bytes a serem lidos, esse método retornará -1;
É recomendável que k seja o valor de bytes lidos. Assim, os elementos b[off+k] até b[off+len-1]
permanecerão inalterados, já que os elementos b[off] até b[off+k-1] serão aqueles que
armazenarão os bytes lidos.
Lembre-se que os elementos b[0] até b[off] e elementos b[off+len] até b[b.length-1] não
sofrem alterações, em qualquer caso.
•• skip(long n)
O método skip(n) tem como função pular e ignorar n bytes de dados do stream de entrada.
Ele retorna o número atual de bytes pulados.
Além disso, skip(n) cria um array de byte e, assim, realiza uma leitura repetitiva deste, a qual
é feita até o momento da leitura completa dos n bytes ou até que se tenha atingido o final do
stream.
O parâmetro de skip(n) é n, que é o número de bytes que serão pulados. Caso o valor de n
seja negativo, não teremos nenhum byte pulado.
Java Programmer – Módulo III (online)
16
No uso de tal método, as subclasses são estimuladas a adotarem uma implementação mais
satisfatória.
É possível que o skip(n) acabe pulando quantidades menores de bytes, o que pode ser
provocado por diferentes fatores. Um deles é atingir o final do stream antes dos n bytes terem
sido pulados.
•• available()
Este método é responsável por retornar o número de bytes que podem ser lidos ou pulados a
partir desse stream sem que o próximo chamador (caller) de um outro método os bloqueie.
•• close()
Tal qual no stream de saída, o método close() tem a finalidade de fechar o stream de entrada,
e sua sintaxe é a seguinte:
Com o stream fechado, os recursos de sistema que estavam sendo usados por ele são
disponibilizados para outras tarefas.
•• mark(int readlimit)
Este método estabelece a posição atual no stream de entrada. Esta posição definida será
considerada por uma nova chamada ao método de reinicialização (reset), que ajustará o
stream de entrada na mesma posição estabelecida anteriormente. O resultado será a releitura
dos mesmos bytes.
Arquivos – I/O e NIO
17
O mark adota como parâmetro o readlimit, que define a quantidade máxima permitida de
bytes que podem ser lidos até o momento em que a posição marcada no stream não seja mais
válida. A sintaxe de mark (readlimit) é a seguinte:
De acordo com o contrato geral do método mark, os bytes que foram lidos depois da chamada
marcada serão memorizados pelo stream caso o método markSupported tenha true como
resposta. Assim, se houver uma próxima chamada do método reset, os mesmos bytes lidos
depois da chamada marcada serão considerados pelo stream.
Antes de o método reset ser chamado, se forem lidos mais bytes que a quantidade especificada
pelo parâmetro readlimit, o stream de entrada não será solicitado para lembrar de dados lidos.
•• reset()
O contrato geral do método reset() estabelece que ele proceda distintamente, dependendo de
qual resposta, true ou false, o método markSupported retornará. A tabela a seguir apresenta
as condições estabelecidas para cada uma das respostas:
Java Programmer – Módulo III (online)
18
Resposta do método
Resultado
markSupported
Uma IOException é lançada caso:
•• markSupported()
O método markSupported() verifica se o stream de entrada está apto a aceitar o uso dos
métodos mark e reset. Sua sintaxe é a seguinte:
O stream de entrada apresenta um objeto específico que oferece uma propriedade constante
de uma instância particular de stream de entrada responsável por dizer se os métodos mark
e reset poderão ser usados nesta stream.
Se o método em questão retornar true, o objeto stream aceita mark e reset. Se retornar false,
estes dois métodos não poderão ser utilizados.
A disponibilidade dos arquivos que fornecerão os bytes de entrada está ligada diretamente ao
ambiente host em que se está trabalhando.
1.1.4.1. Classe FileReader
O recurso mais acessível para ler arquivos de texto é a classe FileReader, representada da
seguinte forma:
Como recurso adicional à FileReader, você pode usar a classe BufferedReader para
desempenhar a tarefa de ler cada uma das linhas do stream.
Java Programmer – Módulo III (online)
20
1.1.4.2. Classe BufferedReader
A classe BufferedReader proporciona uma leitura mais eficaz dos caracteres, já que possibilita
ler não apenas estes caracteres, mas também as linhas e os arrays de caracteres. Sua sintaxe
é a seguinte:
Veja o exemplo seguinte, no qual a entrada do arquivo especificado está sendo armazenada
em buffer:
1.1.5. Classe RandomAccessFile
A classe RandomAccessFile cria um stream de arquivo de acesso aleatório, por meio do qual
é possível ler ou gravar um ou diversos bytes de informações em uma determinada posição de
um arquivo (especificado pelo argumento File). Estas informações podem ser do tipo primitivo
ou string de caracteres Unicode (UTF).
É possível criar objetos da RandomAccessFile sob diferentes modos. Assim, um objeto criado
sob o modo r permitirá que o arquivo seja aberto unicamente para leitura. Caso o arquivo não
exista, será lançada uma FileNotFoundException.
Este construtor gera um stream de arquivo de acesso aleatório, o qual pode tanto ler como
gravar o arquivo especificado pelo argumento File.
Este construtor gera um stream de arquivo de acesso aleatório, que pode tanto ler como
gravar um arquivo com o nome especificado.
Você pode utilizar ponteiros para indicar locais relativos ao diretório inicial, no sistema. Este
procedimento é possível, pois não há como modificar o diretório atual.
1.2. NIO
Na versão 1.4, foram introduzidas novas APIs de I/O (NIO), as quais fornecem novos recursos
e melhor desempenho nas áreas de gerenciamento de arquivos, de buffer, rede escalável e
arquivos I/O, suporte para conjunto de caracteres e correspondência de expressões regulares.
Além disso, as APIs NIO complementam as especificações de I/O no pacote java.io.
•• Files: Classe utilitária responsável pela manipulação de um Path como busca de arquivos;
•• Canais de vários tipos: Representam conexões com as entidades que realizam operações
I/O;
1.2.1. java.nio.file.Path
A nova API NIO traz o pacote java.nio.file e suas principais classes Path, Paths e Files, em que
temos uma reformulação das rotinas antes executadas pela classe java.io.File.
Assim como a classe File, um Path representa um arquivo ou diretório do sistema operacional.
Para criar um Path utilizamos uma das sintaxes a seguir:
Método Descrição
Método Descrição
Veja um exemplo:
1.2.2. Buffers
As classes Buffer utilizadas em todas as APIs NIO são definidas pelo pacote java.nio.
Um buffer pode ser definido como um recipiente para uma quantidade fixa de dados de um
tipo primitivo específico. Além disso, tem uma posição, que é o índice do elemento seguinte
a ser lido ou gravado, e um limite, que é o índice do primeiro elemento que não deve ser lido
ou gravado. Além da posição e do limite, a classe Buffer base também define métodos de
liberação, lançamento, reversão e marcação da atual posição, bem como de ajuste da posição
à marcação anterior.
Você deve saber que para cada tipo primitivo não booleano existe uma classe Buffer. Cada
classe define:
•• Métodos estáticos que alocam um novo buffer, bem como envolvem um array existente
em um buffer.
Os buffers de byte podem ser usados como fontes e destinos de operações I/O. Além disso,
suportam diversos recursos, que não são encontrados em outras classes de buffer:
•• Você pode alocar um buffer de byte como um buffer direto. Neste caso, a Java Virtual
Machine fará um esforço para executar operações nativas I/O diretamente nele;
•• Também é possível criar um buffer de byte pelo mapeamento de uma região de um arquivo
diretamente na memória. Se for este o caso, são disponibilizadas algumas operações
adicionais relacionadas a arquivos, definidas na classe MappedByteBuffer;
•• Lembre-se que um byte buffer fornece acesso tanto a seu conteúdo como a uma sequência
heterogênea ou homogênea de dados binários de qualquer tipo primitivo não booleano,
em qualquer ordem de bytes big-endian ou little-endian.
Classe Descrição
CharBuffer
DoubleBuffer
FloatBuffer Definem métodos get/put, bem como métodos de
IntBuffer compactação, alocação e de envoltório.
LongBuffer
ShortBuffer
1.2.3. Charsets
O pacote java.nio.charset define a API charset, além de definir decodificadores e codificadores,
que fazem a tradução entre bytes e caracteres Unicode.
Além de definir métodos para a criação de coders para um determinado charset e para recuperar
os vários nomes associados a um charset, a classe Charset define métodos estáticos para
testar se um determinado charset é suportado, para localizar instâncias charset pelo nome e
para a construção de um mapa que contém cada charset suportado pela Java Virtual Machine.
Além disso, também foram feitas pequenas alterações nas classes InputStreamReader e
OutputStreamWriter, a fim de permitir que os objetos charset explícitos fossem especificados
na construção de instâncias dessas classes.
Lembre-se que passar um argumento nulo para um construtor ou método de qualquer classe
ou interface neste pacote irá lançar NullPointerException.
Classe Descrição
1.2.4. Channels
O pacote java.nio.channels define tanto os canais como os seletores de APIs, para operações
I/O multiplexed sem bloqueios.
Classe Descrição
Definemétodosestáticosquesuportamainteroperabilidade
Channels das classes de stream do pacote java.io com as classes de
canais deste pacote.
Arquivos – I/O e NIO
31
1.2.4.1. FileChannel
Use esta classe se quiser definir métodos para forçar atualizações para o arquivo a ser gravado
no dispositivo de armazenamento que o contém, para a transferência eficiente de bytes entre
o arquivo e outros canais e para mapear uma região do arquivo diretamente na memória.
Você pode criar a classe FileChannel invocando um de seus métodos estáticos abertos,
ou invocando o método getChannel de um FileInputStream, FileOutputStream ou
RandomAccessFile. Isto retornará um canal de arquivo conectado ao mesmo arquivo
subjacente, como a classe java.io.
Seletores, canais selecionáveis e chaves de seleção fornecem I/O multiplex sem bloqueio, que
é mais escalável do que I/O com bloqueio orientado por threads.
Você deve saber que um seletor é um multiplex de canais selecionáveis. Os canais selecionáveis
são um tipo especial de canal que pode ser sem bloqueio. Assim, se você quiser executar
operações I/O multiplex, deve criar um ou mais canais selecionáveis, sem bloqueio, e registrá-
los como seletor.
Ao fazer isso, você especifica o conjunto de operações I/O que serão testadas para a prontidão
pelo seletor, o que retorna uma chave de seleção que representa o registro.
Java Programmer – Módulo III (online)
32
Após registrar alguns canais como um seletor, você pode realizar uma operação de seleção
para descobrir quais canais estão prontos para realizar uma ou mais operações previamente
declaradas.
Então, se houver um canal pronto, a chave retornada quando do registro será adicionada ao
conjunto de chaves de seleção do seletor. É possível examinar o conjunto de chaves, e as
chaves no seu interior, para determinar as operações para as quais cada canal está pronto.
Cada chave torna possível recuperar o canal correspondente, a fim de executar quaisquer
operações I/O que sejam necessárias.
Note que uma chave de seleção indica se o canal está pronto para alguma operação, mas
isto não garante que uma operação possa ser realizada por um thread, sem que este seja
bloqueado. Assim, é de extrema importância que o código que executa I/O multiplex seja
escrito de forma a ignorar canais que não estejam prontos para realizar as operações.
Classe Descrição
☐☐ a) write()
☐☐ b) print()
☐☐ c) flush()
☐☐ d) close()
5. O que é um buffer?
2.1. Introdução
Em Java, uma asserção é uma declaração que permite testar suposições sobre o programa. Por
exemplo, ao escrever um método de cálculo de velocidade de uma partícula, é possível afirmar
que a velocidade calculada da partícula é menor do que a velocidade da luz.
Cada asserção contém uma expressão booleana, que você supõe ser verdadeira quando
a asserção é executada. Se não for verdadeira, será lançado um erro. Asserções realizam
testes sobre invariantes no código. Ao confirmar a expressão booleana como verdadeira, suas
suposições sobre o comportamento do programa também são confirmadas, o que aumenta a
certeza de o programa não ter erros.
Escrever asserções durante a programação tem se mostrado uma das formas mais rápidas e
eficazes para detectar e corrigir erros. Além disso, você deve usar as asserções para documentar
o funcionamento interno de seu programa, facilitando a manutenção deste.
assert expressão
ou
assert expressão1: expressão2
•• assert expressão
Este é o formato mais simples de asserção. Usa um argumento que é uma expressão booleana,
a qual é a suposição que o programador acredita ser verdadeira. Se a expressão for verdadeira,
a aplicação continuará sendo processada. Caso contrário, uma exceção AssertionError será
gerada.
Usa dois argumentos. No primeiro, você tem a expressão booleana. No segundo, um valor
ou resultado obtido da execução de um método. Este valor ou resultado é passado para o
construtor da classe AssertionError.
A segunda expressão fornece uma mensagem em string que mostra um rastreamento de pilha
com informações de debug mais detalhadas. Se, durante a asserção, a segunda expressão não
fornecer nenhum valor, um erro será lançado.
Asserções
41
ou
ou
Para ativar ou desativar asserções no Eclipse, acesse Run / Run Configurations e, na guia
Argument, adicione o argumento –ea ou –da no campo VM arguments:
Java Programmer – Módulo III (online)
42
Todavia, na execução de uma aplicação, você pode desativar a asserção para determinadas
classes e/ou pacotes e ativá-la para outros, em um contexto seletivo.
Para selecionar em quais classes e pacotes deseja usar as asserções, empregue switches de
linhas de comando.
Para habilitar ou desabilitar a asserção em todas as classes existentes, com exceção das classes
de sistema, basta apenas não inserir nenhum argumento na linha de comando.
Apesar de compartilhar diretórios com o(s) pacote(s) que o contém, um subpacote é totalmente
independente dele(s).
java –ea
Habilita asserções.
java –enableassertions
java –da
Desabilita asserções.
java –disableassertions
Você deve atentar para o fato de que um uso de asserção considerado legal nem sempre é
apropriado e vice-versa.
Uma asserção apropriada é referente aos procedimentos que deveriam ser utilizados na
asserção, e não aos que poderiam ser adotados. O uso correto de asserções é considerado
como uso apropriado, e não como uso legal, pois nem todos os usos válidos das asserções
são considerados apropriados.
Embora seja válido tentar recuperar e manipular uma falha de assertiva, já que AssertionError
é uma subclasse de Throwable e, com isso, pode ser capturada, tais procedimentos não são
recomendados.
A Oracle considera as seguintes regras como fundamentais para que as asserções sejam
apropriadamente utilizadas:
Você pode utilizar assert para testar a lógica dos códigos que chamarão um método privado,
como mostrado a seguir:
Destacamos que, ao criar um método privado, é bem provável que você tenha controle de
todos os pontos que invocam o método, tendo em vista não estar disponível na interface
pública de seu código.
Asserções
45
•• Asserções não devem ser usadas para validar argumentos de linha de comando
Assim como você não deve validar argumentos de métodos públicos com asserções, também
não é apropriado validar argumentos de linha de comando com asserções. Neste caso, o uso
de um mecanismo de exceção provavelmente cuidará da validação dos argumentos de linha
de comando.
•• Asserções devem ser usadas para checar instruções case, que você sabe que nunca
ocorrerão, nem mesmo em métodos públicos
Veja o seguinte bloco de código, que esclarece o que a regra em questão quer dizer:
O exemplo anterior (uma instrução switch padrão), em que o valor a deve ser igual a 4, 6 ou
8, caracteriza-se por um bloco de código que nunca poderá ser alcançado. Mesmo sabendo
disso, você pode utilizar o assert false para lançar uma AssertionError caso o bloco seja
atingido. A AssertionError provocará uma falha da asserção e da suposição.
Java Programmer – Módulo III (online)
46
•• Expressões assertivas que possam causar efeitos colaterais jamais devem ser usadas
Expressões assertivas que possam alterar o estado que um objeto apresentava anteriormente
ao uso jamais devem ser utilizadas. A única mudança de execução aceitável em um programa,
quando do uso de asserções, é o lançamento de uma AssertionError no caso de uma suposição
falsa.
2.6. Classe AssertionError
A AssertionError é uma classe que tem como função informar que uma asserção falhou.
2.6.1. Construtores
Veja, a seguir, a descrição dos construtores da classe AssertionError:
Construtor Descrição
Construtor Descrição
☐☐ a) -da
☐☐ b) -stopassertions
☐☐ c) -break
☐☐ d) -sa
☐☐ d) Asserções devem ser usadas para checar instruções try, que você
sabe que nunca ocorrerão, nem mesmo em métodos públicos.
☐☐ a) palavra-chave, variável.
☐☐ b) objeto, variável.
☐☐ c) palavra-chave, identificador.
☐☐ d) objeto, identificador.
3.1. Introdução
Thread é uma classe em Java que permite a execução de diferentes tarefas simultaneamente.
O termo thread diz respeito a linhas que compartilham um mesmo contexto e cuja execução
ocorre de forma concomitante.
Uma thread representa um fluxo de controle em um processo, ou seja, cada thread em execução
dentro de um programa apresenta:
•• Um início;
•• Uma sequência;
•• Um ponto de execução;
•• Um final.
3.2. Programação multithreaded
O conceito de multiprocessamento é bastante importante para a compreensão do conceito
de multithreaded. Multiprocessamento refere-se à utilização compartilhada da CPU por vários
processos, os quais levam um determinado período de tempo para serem executados. Essa
execução simultânea de vários processos ocorre em grande parte dos sistemas atuais, que
possuem diversos processadores ou núcleos de processamento, capazes de realizar tarefas
de forma concomitante. A programação multithreaded procura tirar vantagem dos múltiplos
processadores disponíveis hoje em dia nos computadores modernos.
Exceto nas situações em que há o IPC (Inter Process Communication), um mecanismo que
permite a comunicação entre os processos, estes são mantidos isoladamente a fim de que não
haja riscos de um processo afetar outros e, também, para evitar os GPFs (General Protection
Faults).
Como você já viu, threads são linhas de execução independente cujo início é realizado de
maneira mais rápida do que os processos mencionados e cujo acesso à área de dados própria
é efetuado como um programa único. Você pode implementar threads na Java Virtual Machine
de duas maneiras distintas:
•• A velocidade em que os arquivos são lidos e escritos é mais baixa do que a capacidade da
CPU em processá-los;
•• A entrada fornecida pelos usuários é realizada com uma velocidade menor do que a do
computador.
O programa precisa aguardar a conclusão de cada uma dessas tarefas para poder iniciar a
outra quando se encontra em um ambiente no qual há uma única thread, fazendo com que
haja ociosidade da CPU na maior parte do tempo. Isso é resolvido pelo multithreading.
Tendo em vista que é possível definir um processo como sendo um programa em execução,
multitarefa baseada em processo é definida como um recurso capaz de permitir que um micro
execute simultaneamente dois ou mais programas. Este é o tipo de multitarefa mais conhecido.
Um programa, neste tipo de multitarefa, é definido como a menor unidade de código que
o scheduler é capaz de despachar. O scheduler é o agendador de tarefas. Sendo assim, a
multitarefa baseada em processos trabalha com a execução de uma forma mais ampla.
Visto que o processo é uma tarefa pesada, a multitarefa baseada em processos exige uma
grande quantidade de recursos. Além disso, a comunicação interprocessos envolve custos e,
ainda, apresenta certas limitações. O que também envolve grandes custos é o chaveamento de
contexto entre processos. Vale destacar, também, que um processo necessita de um espaço
de endereços próprio.
Threads
57
Na multitarefa baseada em threads, uma thread é definida como a menor unidade de código
que o scheduler é capaz de executar, ou seja, ele permite que um programa execute duas ou
mais tarefas de forma simultânea.
A multitarefa baseada em threads exige uma quantidade menor de recursos do que a exigida
pela multitarefa baseada em processos, uma vez que as threads, ao contrário dos processos,
são tarefas leves. Além disso, um único espaço de endereço e um único processo são
compartilhados pelas threads.
3.3. Implementando multithreading
Quando você trabalha com a linguagem Java, a implementação do recurso multithreaded pode
ser realizada de duas formas distintas: por meio da implementação da interface Runnable ou
por meio da extensão da classe Thread.
Quanto à criação de threads, esta toma como base o fato de cada thread ser capaz de executar
o método run(), seja este o seu próprio método ou o método de outros objetos. Sendo assim,
a criação de threads pode ser realizada:
Quanto à implementação da interface Runnable, esta se torna mais adequada por conta de
uma única thread ser capaz de executar as tarefas necessárias, pois, dessa forma, a classe
poderá estender outra classe.
É a partir de uma instância de java.lang.Thread que uma thread é iniciada, cujo gerenciamento;
isto é, criação, inicialização e paralisação; é realizado por métodos presentes na classe Thread.
Neste caso, os principais métodos são: run(), start(), sleep(), yield(). O método run(), é
utilizado para iniciar uma tarefa.
Nas situações em que você deseja executar uma tarefa em segundo plano, ou seja, executar
uma tarefa na própria thread, essa thread será a executora do código. Vale destacar que o
código sempre deve ser escrito em uma thread separada do método run().
Embora o método run() realize a chamada de diversos métodos, o primeiro método a ser
chamado pela thread de execução sempre será o run(). Isso significa que o método run()
entrará em uma das classes utilizada com a finalidade de determinar a tarefa da thread.
3.3.1. java.lang.Thread
Estender a classe java.lang.Thread significa criar uma classe a partir dela, ou seja, criar uma
classe que obtenha a herança de java.lang.Thread. A partir desta criação, realize o seguinte
procedimento:
A subclasse de java.lang.Thread pode sobrescrever o método run(), embora isso não seja
obrigatório, uma vez que a classe em questão fornece uma implementação para esse método.
Tal implementação, entretanto, não tem quaisquer funções, o que faz com que a classe Thread
sobrescreva o método run() com a finalidade de colocar em seu lugar o código a ser executado.
Threads
59
Estender a classe Thread e sobrescrever o método run() são duas tarefas realizadas para
definir que um código seja executado em uma thread à parte, como demonstrado no exemplo
seguinte:
O exemplo apresentado, porém, tem algumas limitações, pois, ao estender a classe Thread,
não será possível estender outras classes. Ainda, destacamos que a herança da classe Thread
não traz um comportamento estritamente necessário, uma vez que a utilização de uma thread
depende de sua instanciação.
O exemplo descrito a seguir demonstra como sobrecarregar o método run() em uma classe
estendida de Thread. Observe:
Nesse exemplo, String segmento refere-se ao parâmetro do método run() sobreposto, o qual
será invocado apenas se uma chamada explícita for efetuada. Sendo assim, o método em
questão não será utilizado com a base para nova pilha de chamadas.
3.3.2. java.lang.Runnable
Uma thread também pode ser criada por meio da execução do método public void run()
pertencente a algum objeto que esteja fora da árvore de herança de java.lang.Thread. Porém,
tendo em vista que a thread é sempre representada por uma subclasse de java.lang.Thread,
a implementação deve ser realizada por meio do construtor Thread(Runnable r) da classe
Thread. Isto para que essa thread seja capaz de executar o código do objeto de outra classe.
Esse construtor Thread recebe uma referência a um objeto responsável por implementar a
interface java.lang.Runnable. Tal referência é o seu parâmetro. Assim que esse construtor é
chamado, ocorre o seguinte:
2. A thread é registrada no escalonador assim que o método start() dessa thread é invocado;
3. O método run() responsável por implementar a interface Runnable é executado assim que
o processador é obtido pela thread.
Threads
61
Este formato demonstra que há um código cujo processamento pode ser realizado por uma
thread de execução, o que ocorre independente do mecanismo selecionado.
3.4. Construtores
Instanciar uma classe Thread é o primeiro passo para criar uma thread de execução, embora
ainda seja necessário um objeto Thread nas situações em que o método run() se encontra
na classe de implementação da interface Runnable e nas ocasiões em que esse método se
encontra em uma classe estendida de Thread.
Para realizar a instanciação da classe Runnable, você deve utilizar o seguinte código:
A fim de criar uma instância de java.lang.Thread para a qual a tarefa deve ser passada, utilize
o código seguinte:
Você pode utilizar um construtor sem argumentos a fim de criar uma thread. Ao fazer isso,
essa thread iniciará o trabalho chamando seu próprio método run(). Nas situações em que
estender a classe Thread, a intenção é que a thread chame seu próprio método. Contudo,
quando você implementa a interface Runnable, faz-se necessário alertar essa thread quanto
à execução de seu método.
Destacamos que somente uma instância de Runnable pode ser passada para diversos objetos
Thread. Como resultado, diversas threads terão como destino uma única instância, o que
indica que uma mesma tarefa será realizada por diversas threads de execução. O código
descrito a seguir demonstra tal situação:
Threads
63
•• Thread( );
•• Thread(Runnable target);
•• Thread(String name);
•• Thread(ThreadGroup group, Runnable target, String name, long stackSize).
Você viu, até este momento, como instanciar uma thread, mas não como iniciá-la. Uma thread
não iniciada ainda não é uma thread de execução. Isso significa que ela está no estado new,
ou seja, ela não é considerada como uma thread ativa.
A fim de verificar se uma determinada thread está ativa, você pode chamar o método isAlive()
na instância da classe Thread. Este método é capaz de definir se uma thread foi iniciada sem
que o seu método run() tenha sido concluído. Para que a thread seja considerada ativa, a JVM
necessita de algum tempo para configurá-la após a chamada do método start(). Uma vez
chamado este método, a thread será considerada inativa somente após ter sido descartada.
Java Programmer – Módulo III (online)
64
3.5. Estados da thread
As threads, que são utilizadas na linguagem Java com a finalidade de manter a sincronia entre
as execuções realizadas em um determinado ambiente, apresentam-se em um determinado
estado. Os estados em que elas podem estar serão descritos na tabela a seguir:
Estado da
Descrição
thread
Uma thread neste estado está pronta para ser executada, mas ainda
não foi selecionada pelo scheduler como sendo uma thread pronta
para o processamento. Quando está no estado ready to run, a thread
Ready to run é considerada ativa. Ela entra neste estado pela primeira vez assim que
seu método start() é chamado, mas pode entrar neste estado em outras
situações, como no momento em que acaba de ser processada, ou quando
retorna de outros estados, como resume, blocked e suspended.
Estado da
Descrição
thread
A thread está neste estado nas situações em que seu método run() é
finalizado, ou seja, nas situações em que a thread está inativa. Threads
Terminated inativas não podem ser reativadas. Sendo assim, caso você chame o
método start() de uma instância de Thread que esteja inativa, receberá
uma exceção de tempo de execução.
A classe Thread contém os métodos stop() e suspend(), os quais permitem que uma
thread informe a outra thread a respeito de sua suspensão. Contudo, esses métodos foram
considerados depreciados e, dessa forma, não devem ser utilizados. Nesse rol de depreciados,
também devem ser incluídos os métodos destroy() e resume().
Nas situações em que se tem um sistema composto por uma única thread, ela utiliza o loop de
eventos com pooling, ou seja, esta única thread é responsável por controlar um loop infinito,
bem como verificar a fila a fim de definir qual será a próxima tarefa a ser realizada. O sinal
retornado pelo mecanismo de pooling faz com que o controle para o manipulador de eventos
adequado seja despachado pelo loop de eventos. Então, o sistema precisa aguardar até que
esse manipulador retorne. Além disso, o processamento de outros eventos pode ser impedido
caso o controle do sistema seja assumido por parte do programa.
Em razão de todos esses fatores, quando uma thread única em um determinado ambiente
fica no estado blocked, ou seja, tem sua execução paralisada enquanto aguarda por um
determinado recurso, a execução de todo o programa também é paralisada durante esse
período. Contudo, esse problema é eliminado pela multithreading da linguagem Java, uma
vez que ela elimina o mecanismo de pooling e loop, permitindo que a eventual paralisação de
uma thread não implique na paralisação de todo o programa. Sendo assim, apenas a thread
em estado blocked de um programa Java tem sua execução paralisada; todas as outras
permanecem em execução.
Vale destacar que a multithreading da linguagem Java permite que um loop de animação realize
um pequeno intervalo entre imagens a fim de tornar a execução mais lenta sem prejudicar o
sistema como um todo.
Java Programmer – Módulo III (online)
66
3.6. Scheduler
O scheduler é o responsável por definir quais threads serão executadas em um dado momento,
além de fazer com que a thread saia do estado ready to run. Nas situações em que somente
uma máquina realiza o processamento, é possível que apenas uma thread seja executada por
vez, sendo que o scheduler é o responsável por determinar qual thread será processada.
É importante saber que, para ser selecionada pelo scheduler, uma thread deve estar qualificada,
ou seja, deve estar no estado ready to run. Embora haja uma fila de threads a serem executadas,
não é possível assegurar que a ordem dessa fila será obedecida. A ordem normal seria a
seguinte:
•• Assim que a execução da thread for concluída, esta passa para o final da fila;
•• Esta thread, então, aguardará até que chegue em primeiro lugar da fila novamente para
que possa ser, mais uma vez, executada.
A fila mencionada é, na verdade, chamada de pool executável, o que demonstra ainda mais
o fato de não haver uma ordem certa de execução. Tal ordem de execução não é garantida
porque o scheduler não pode ser controlado, mas apenas influenciado por meio de alguns
métodos, os quais estão contidos tanto na classe java.lang.Thread, quanto na classe java.
lang.Object.
Uma thread pode desistir do controle de maneira espontânea. Isso pode ser feito de três
formas distintas: a thread pode ceder explicitamente esse controle; ela pode entrar em estado
blocked; ou pode entrar em modo sleep. Quando uma thread cede o controle dessa maneira,
todas as outras threads são analisadas para que seja encontrada aquela cuja prioridade é mais
elevada, e a qual se encontra no estado ready to run. O controle da CPU, então, é passado a
esta thread.
•• Deslocamento de threads
É possível que uma thread seja deslocada por outra cuja prioridade é mais alta. Caso a thread
cuja prioridade é mais baixa não ceda o controle, ela é deslocada por uma de prioridade mais
alta independentemente das tarefas que estiver realizando, uma vez que, teoricamente, uma
thread de prioridade mais elevada pode ser executada no momento necessário. O mecanismo
que permite a execução de thread com prioridade mais alta no momento desejado é chamado
de multitarefa preemptiva.
Há situações nas quais threads que possuem a mesma prioridade disputam os ciclos da CPU.
Grande parte das JVMs possui um scheduler que utiliza um agendamento preemptivo com
base em prioridades. Isso quer dizer que as JVMs utilizam a divisão de tempo, embora sua
especificação não exija a implementação de um scheduler com essa divisão de tempo.
Um scheduler com divisão de tempo permite que um determinado período de tempo seja
alocado a cada uma das threads. Após o término desse período, a thread retorna ao estado
ready to run e, dessa maneira, possibilita a execução de uma outra thread.
Java Programmer – Módulo III (online)
68
Além das JVMs que utilizam um scheduler com divisão de tempo, há aquelas que utilizam
um scheduler capaz de permitir que uma thread seja executada até o momento em que seu
método run() seja finalizado. Entretanto, o scheduler que trabalha com prioridades para as
threads ainda é o mais utilizado pelas Java Virtual Machines. Com este scheduler, é comum
que as threads com prioridade mais elevada sejam executadas em primeiro lugar.
A prioridade é como uma garantia de ordem de execução de threads. Apesar disso, não é
possível confiar que, por conta dessas prioridades, o comportamento de um determinado
programa será o mais adequado. Ainda, você não pode confiar nas prioridades quando projetar
um programa com diversas threads.
Nas situações em que as threads têm prioridades iguais, é papel do scheduler determinar se o
tempo das threads de pool será dividido para que todos tenham oportunidades de execução
equivalentes, ou se uma determinada thread será executada até que entre no estado blocked
ou que seu método run() seja concluído.
Como já apresentado, a configuração das prioridades é realizada com números inteiros, que
geralmente estão no intervalo de 1 a 10. Entretanto, esses valores também não são garantidos,
pois caso existam dez threads cujas prioridades são distintas, e a execução do programa
estiver sendo realizada em uma JVM cujo intervalo alocado é de apenas cinco prioridades, é
possível que para uma mesma prioridade sejam mapeadas duas ou mais threads.
Veja, a seguir, as maneiras pelas quais é possível configurar as prioridades das threads:
Threads
69
Nesse código, s está referenciando uma thread, a qual terá a mesma prioridade da thread
principal. Isso ocorre porque essa thread está executando o código que realiza a criação da
instância de Segmento.
Nesse código, a prioridade da thread é configurada por meio da chamada do método setPriority()
em uma instância de Thread.
3.7.1. Método yield()
A função do método yield() é permitir que uma thread em estado running retorne ao estado
ready to run para que outras threads com a mesma prioridade também possam ser processadas.
Este método, porém, não garante que a thread em execução passe para o estado ready to run
a fim de dar oportunidade de execução à outra thread.
Caso o processador esteja ocupado, o método yield() informa que uma determinada thread não
está em execução e, por isso, a preferência pode ser passada a outra thread. No entanto, caso
o processador não esteja ocupado, essa thread cuja execução estava paralisada é reativada
de forma instantânea.
Com isso, o método em questão é capaz de reduzir o tempo de espera para a execução de
threads.
O método yield(), portanto, deve ser utilizado nas situações em que se deseja executar uma
thread que tem prioridade igual à thread que está em execução no momento.
public static void yield()
Java Programmer – Módulo III (online)
70
3.7.2. Método join()
Este método permite que uma thread seja adicionada ao final de outra thread. Isso significa
que, se uma determinada thread não puder entrar em execução até que outra thread tenha
concluído a sua, é mais adequado adicionar esta primeira thread àquela que está em execução.
Assim, uma thread não será executada até que a outra tenha sido concluída. Este método
aguarda até que a execução da thread para a qual foi chamado seja finalizada.
public final void join()
throws InterruptedException
Algumas outras formas do método join() permitem determinar o período de tempo para que
a thread especificada seja finalizada. O nome atribuído a esse método (join = juntar) deve-se
ao fato de que a thread que o chama aguarda até que a thread especificada junte-se a ela.
3.7.3. Método isAlive()
A função do método isAlive() é determinar se uma thread ainda está em execução. Para isso,
é preciso chamá-lo para a thread desejada. Caso seja chamado para uma thread que ainda
esteja em execução, o valor retornado por isAlive() é true, caso contrário, o valor retornado
é false.
O método isAlive(), que também está localizado na classe Thread, possui a seguinte forma:
3.7.4. Método sleep()
A função deste método é permitir que a thread dentro da qual foi chamado fique no estado
suspended durante um determinado período de tempo, que é definido em milissegundos.
Este método é utilizado com a finalidade de tornar o percurso da thread pelo código mais
lento, bem como para forçar essas threads a dar oportunidade a outras.
O método sleep(), pertencente à classe Thread, força a thread a entrar no estado suspended
antes que ela retorne ao estado read to run. A fim de que volte a este estado, é preciso que
a thread seja despertada. É importante ter em mente, porém, que o fato de a thread ter sido
despertada não significa que ela será executada, uma vez que ela retorna ao estado read to
run, tendo que aguardar até que possa entrar no estado running.
Threads
71
O tempo que é determinado por meio do método sleep() refere-se somente ao período mínimo
em que a thread não será executada, o que faz deste método um timer suficiente, porém,
impreciso, uma vez que não é possível garantir que a execução da thread será iniciada no
momento em que o período determinado finalizar ou, ainda, quando essa thread for despertada.
O sleep() é um método estático que pode ser colocado em qualquer local dentro do código,
pois todo esse código será executado por uma thread e, assim que a execução do código
atingir a chamada deste método, a thread atual entrará em estado suspended.
Em que:
•• InterruptedException: É uma exceção que pode ser lançada pelo método sleep().
O método sleep() também possui uma outra forma, mas esta apenas é útil nos ambientes
em que a determinação do período de suspensão pode ser realizada em milissegundos e em
nanossegundos. A forma de utilização em questão é:
Depois de compilado e executado o código anterior, o resultado será como o exibido na figura
seguir:
3.8. Sincronização
Os recursos compartilhados apenas podem ser utilizados por uma thread de cada vez, porém,
no decorrer da execução de alguns programas, é possível que haja mais de uma thread
necessitando de acesso a esses recursos. Para resolver essa situação, utilize a sincronização.
O monitor de bloqueio de acesso, definido como um objeto cuja função é ser uma trava mútua,
é um conceito bastante importante para o processo de sincronização de threads. Apenas uma
thread por vez pode estar com esse monitor.
Assim que uma thread consegue acessar o recurso desejado, o acesso a ele é bloqueado às
outras threads. Quando isso acontece, dizemos que a thread entrou no monitor, e qualquer
outra thread que tentar entrar nele ficará em estado suspended até que a thread inicial deixe
o monitor. A thread encontrada no monitor pode entrar novamente nele se desejar.
3.8.1. Palavra-chave synchronized
Quando synchronized é utilizada na declaração de um método, somente uma thread por vez
pode acessar esse método, ou seja, antes da execução desse método, um monitor de bloqueio
de acesso é adquirido. Este monitor de bloqueio é o próprio objeto ao qual o método pertence,
ou a classe, caso seja estático.
Threads
73
3.8.1.1. Race condition
Race condition é o nome dado a uma situação na qual um mesmo conjunto de dados é
manipulado simultaneamente por diversos processos, e em que temos um resultado
determinado pela ordem que tais processos realizaram seus acessos aos dados.
Mais especificamente, temos uma race condition nas situações em que mais de uma thread
acessa o mesmo método de forma concomitante, causando uma competição por esse método.
As consequências de uma race condition podem ser desastrosas, mas também há momentos
em que um programa funciona de forma adequada apesar desse problema. Isso acontece
porque uma race condition pode ocorrer de forma discreta, bem como não pode ser prevista,
uma vez que o momento em que um chaveamento de contexto ocorrerá não é garantido.
O código a seguir mostra como solucionar os problemas causados por uma race condition.
Essa solução envolve a restrição do acesso ao método a apenas uma thread por vez:
O exemplo apresenta a definição do método com synchronized, o que evita que outras threads
chamem um método enquanto este estiver sendo utilizado.
3.8.2. Bloco sincronizado
O processo de sincronização deve ser realizado somente sobre o trecho de código necessário
para a proteção dos dados, uma vez que tal processo exclui a possibilidade de haver
concorrência. Sendo assim, caso um método apresente um escopo maior do que o necessário
para sincronização, você pode reduzir tal escopo de forma a sincronizar apenas uma parte
desse método, ou seja, apenas um bloqueio, o qual é chamado de bloco sincronizado.
Java Programmer – Módulo III (online)
74
É preciso determinar o bloqueio de objeto que será utilizado quando você sincroniza um bloco
de código, o que permite ter, em um único objeto, mais de um bloqueio para o processo de
sincronização de códigos.
Quanto aos métodos estáticos, estes podem ser sincronizados, porém, essa sincronização
precisa de um único bloqueio para toda a classe. Esse bloqueio é o da instância de java.lang.
Class, a qual representa cada classe carregada em Java. A sincronização de métodos estáticos
é feita da seguinte maneira:
A criação de blocos sincronizados é a solução encontrada nas situações em que classes não
podem ser alteradas a partir da declaração de seus métodos como sincronizados. Os blocos
sincronizados permitem a sincronização do acesso aos objetos de uma classe cuja criação não
foi realizada a partir dos conceitos de execução multithreaded.
Threads
75
Vale destacar que um bloco sincronizado garante que um método membro de um objeto
apenas será chamado no momento em que a thread atual entrar no monitor desse objeto. Ao
criar um bloco sincronizado, é possível inserir nele as chamadas ao método da classe que não
é sincronizada, tal como demonstrado a seguir:
synchronized(objeto){
Os blocos não precisam ser colocados entre chaves { } quando for realizada a sincronização
de comandos. Ressaltamos que o código descrito anteriormente (objeto) refere-se ao objeto a
ser sincronizado.
3.9. Bloqueios
Bloqueios e sincronização são dois conceitos relacionados entre si, uma vez que a sincronização
funciona com os bloqueios. Há algumas questões importantes a respeito de ambos que devem
ser levadas em consideração:
•• Os bloqueios de uma thread que entra em estado suspended também entram nesse
estado;
•• Uma classe não precisa que todos os seus métodos sejam sincronizados. Sendo assim,
pode haver tanto métodos sincronizados quanto métodos não sincronizados em uma
classe;
•• Nas classes que possuem tanto métodos sincronizados quanto não sincronizados, estes
poderão ser acessados por diversas threads;
•• Quando em uma classe são sincronizados dois métodos, somente um poderá ser acessado
por uma única thread.
Java Programmer – Módulo III (online)
76
Os objetos da linguagem Java contêm um único bloqueio interno. Assim que o código de
método desse objeto é sincronizado, esse bloqueio surge. Sendo assim, quando esse bloqueio
é utilizado por uma determinada thread, os métodos sincronizados desse objeto não poderão
ser acessados por qualquer outra thread. Esse bloqueio será liberado quando a thread sair do
método sincronizado e outra entrar nele.
Em suma, quando o bloqueio de um objeto está sendo utilizado por uma thread, outra thread
não poderá utilizar um método sincronizado desse objeto.
É preciso destacar que a sincronização deve ser realizada somente sobre os métodos que
acessam dados a serem protegidos, uma vez que ela tem grande impacto sobre a performance.
Embora o bloqueio de um objeto possa ser acessado por uma thread de cada vez, cada uma
dessas threads pode acessar mais de um objeto por vez. Esses bloqueios, então, são liberados
gradativamente com o desenrolar da pilha.
Dessa forma, é possível que a thread obtenha o objeto e, ainda, utilize-o para chamar um
método sincronizado presente nele, pois a JVM sabe que a thread em questão já possui o
bloqueio.
Existem métodos que mantêm o bloqueio e existem métodos e classes que o liberam. Veja
quais são esses métodos e classes:
•• Classe java.lang.Thread;
•• Método wait().
•• Método join();
•• Método notify();
•• Método sleep();
•• Método yield().
O método notify() pode tanto manter quanto liberar o bloqueio. Ele o libera quando a thread
sai do código sincronizado após o método ter sido chamado.
Threads
77
3.10. Deadlock
Um deadlock ocorre nas situações em que duas threads dependem uma da outra em um par
de objetos que estão sincronizados. Em situações como esta, temos uma ou mais threads cuja
execução está paralisada, esperando pelo acesso a um recurso que já foi alocado por outra
thread.
O impasse ocorre porque a thread do objeto Y deveria liberá-lo para que a thread do objeto X
pudesse chamar seus métodos, bem como a thread do objeto X deveria liberá-lo para que a
thread do objeto Y pudesse chamar um de seus métodos. Essas threads ficarão aguardando
eternamente, uma vez que não podem ser executadas até que liberem seus bloqueios.
Entretanto, é bastante provável que o deadlock não aconteça em razão de a CPU passar da
thread de leitura para a thread de gravação em um ponto determinado. A fim de eliminar
qualquer possibilidade de impasse, basta inverter a ordem do bloqueio da thread de leitura e
da thread de gravação, neste caso, linhas 11 e 12 ou linhas 19 e 20.
É importante atentar para o fato de que um deadlock dificilmente ocorre, pois, para que ele
aconteça, é preciso que duas threads estejam compartilhando o tempo da CPU da mesma
maneira. Além disso, o deadlock pode envolver mais de duas threads e mais de dois objetos
sincronizados, uma situação ainda mais complexa.
É importante destacar que para uma thread poder chamar os métodos wait(), notify() e
notifyAll() de um determinado objeto, é necessário que, primeiramente, ela tenha o bloqueio
desse objeto.
Tendo em vista que wait() e notify() são métodos de instância da classe Object, é possível que
haja uma lista de threads aguardando o recebimento de uma notificação de um objeto. Para
que uma thread entre nessa lista, o método wait() do objeto de destino deve ser executado
e, quando isso acontece, é preciso que o método notify() do objeto de destino seja chamado
para que essa thread possa executar alguma outra instrução. Somente uma thread poderá
continuar sua execução caso haja várias threads esperando em um mesmo objeto. Caso
contrário, nenhuma ação ocorrerá.
Threads
79
Observe o código a seguir. Ele demonstra um objeto aguardando pela notificação de outro:
•• Na linha 6, o método y.start() será chamado, o que fará com que SegmentoX passe para
a linha de código seguinte de sua classe;
•• Na linha 11, é utilizado o método y.wait() para que SegmentoX não chegue até a linha 16
antes que SegmentoY possa finalizar seus cálculos;
Java Programmer – Módulo III (online)
80
•• Nas linhas de 9 a 14, o método wait() está sendo encapsulado por um bloco try-catch.
Este código esperará até que o método notify() seja chamado no objeto ObjetoA. O código
anteriormente descrito mostra que, enquanto a thread estiver esperando, ela libera o bloqueio
para outras threads, mas, para que retorne à sua execução, ela necessita novamente desse
bloqueio. O método notify() fará a notificação a qualquer thread que esteja aguardando no
objeto this.
Threads
81
É importante ressaltar que somente uma thread que possui o bloqueio do objeto pode chamar
os métodos wait() e notify(). Caso essa thread não possua o bloqueio, será lançada uma
exceção IllegalMonitorStateException, a qual não precisa ser capturada de forma explícita,
uma vez que não é verificada. Essa exceção, porém, deve ser manipulada, pois uma thread
em espera, assim como uma thread no estado suspended, pode ser interrompida a qualquer
momento. O código descrito a seguir mostra como fazer essa manipulação:
Caso a thread não seja interrompida, ela pode continuar sua execução até que receba uma
notificação, ou até a expiração do prazo de espera. Esse prazo máximo de espera, determinado
pelo método wait(), também pode ser estabelecido em milissegundos, o que é feito da seguinte
maneira:
Uma thread libera prontamente seu bloqueio assim que o método wait() é chamado em um
objeto, porém, isso não acontece necessariamente quando o método notify() é chamado.
Neste caso, o bloqueio apenas será liberado no momento em que a thread tiver concluído o
código sincronizado.
Já o método notifyAll() permite notificar todas as threads que aguardam em um determinado
objeto. Ao utilizar esse método em um objeto, você permite que as threads saiam do estado
de espera e retornem ao estado ready to run:
Assim que todas as threads são notificadas, elas iniciam uma competição em busca do bloqueio.
Dessa forma, as threads entram em ação sem que haja a necessidade de uma notificação
adicional, uma vez que o bloqueio será utilizado e liberado por cada uma dessas threads.
Java Programmer – Módulo III (online)
82
Utilizar o método notifyAll() é importante principalmente nas situações em que você tem
diversas threads aguardando em um objeto ou, ainda, quando deseja que a thread certa seja
notificada. Com o método notify(), somente uma thread será notificada, impossibilitando
assegurar que esta será a thread certa.
O quadro a seguir apresenta uma relação dos principais métodos utilizados na manipulação
de threads:
Método
Métodos Métodos
definido Métodos
definidos definidos Métodos
na não
na classe na classe estáticos
interface estáticos
Object Thread
Runnable
join()
notify()
sleep() sleep() join()
run()
notifyAll()
start() yield() start()
wait()
yield()
Os métodos notify(), notifyAll() e wait() são definidos na classe Object, e não na classe
Thread, porque eles atuam sobre os monitores, e não sobre as threads. Os monitores, como
você viu, referem-se a objetos que estão relacionados às instâncias de uma classe.
Para que essa notificação seja recebida por todas as threads, é preciso que o método notifyAll()
seja chamado. Sendo assim, é preciso que esses métodos sejam chamados em uma thread
que possua o monitor do objeto ao qual ela pertence. Vale ressaltar que, em razão disso,
esses métodos devem ser invocados em blocos sincronizados, ou em outros métodos.
3
Teste seus conhecimentos
Threads
Java Programmer – Módulo III (online)
84
☐☐ d) Uma thread nesse estado está pronta para ser executada, mas
ainda não foi selecionada pelo scheduler como sendo uma thread
pronta para o processamento.
☐☐ d) Uma thread nesse estado está pronta para ser executada, mas
ainda não foi selecionada pelo scheduler como sendo uma thread
pronta para o processamento.
4.1. Introdução
O JDBC (Java Data Base Conectivity) é uma especificação elaborada no Java para prover a
acessibilidade de aplicações com bancos de dados por esta linguagem, cuja primeira versão
foi criada em 1997.
Esta API proporciona um acesso aos bancos de dados de forma fácil e simples, especialmente
acesso a dados armazenados em um banco de dados relacional. Trata-se de um padrão de
acesso a dados obedecido pela indústria de bancos de dados.
A fim de seguir este padrão, os fabricantes devem distribuir drivers JDBC aos desenvolvedores
Java.
Com JDBC, você pode escrever aplicativos Java para gerenciar estas três atividades de
programação:
•• O pacote javax.sql, referido como o pacote opcional da API JDBC, o qual estende a
funcionalidade da API JDBC a partir de uma API do lado do cliente (client-side) para uma API
do lado do servidor (server-side), e é uma parte essencial da tecnologia de Java Enterprise
Edition (Java EE).
Em geral, um driver é composto por um ou mais arquivos .jar que devem ser incorporados à
sua aplicação (ao classpath do Java) para garantir o acesso àquela base de dados.
Uma vez que muitos dos novos recursos são opcionais e, consequentemente, há alguma
variação em drivers e as características que eles suportam, você deve sempre verificar a
documentação do driver para ver se ele suporta um recurso antes de tentar usá-lo.
JDBC
89
4.2. Pacote java.sql
A API JDBC foi configurada para que você possa executar instruções SQL em um banco de
dados, bem como acessar e executar stored procedures e outras rotinas programáticas.
Para isso, utilize um pacote chamado java.sql. Com ele, você pode acessar e, também,
processar os dados que estão armazenados em uma fonte de dados, bem como abrir tabelas,
além de realizar diversas operações, como inclusão, alteração, exclusão de dados e consultas
aos dados de uma tabela.
O acesso às diferentes fontes de dados é realizado por meio de drivers distintos, instalados
de forma dinâmica, e que estão incluídos na framework da API.
4.3.1. Carregando drivers
A comunicação com o banco de dados é realizada a partir de um driver, que é um conjunto
de classes Java. Por meio dos drivers, você pode realizar tarefas como a criação de conexões
e tabelas, a chamada de Stored Procedures e a execução de comandos SQL.
Java Programmer – Módulo III (online)
90
Antes de efetuar a conexão com o banco de dados, é necessário que o driver JDBC seja colocado
no CLASSPATH. Caso o banco de dados seja o MySQL, você deve copiar o arquivo .jar do
MySQL para as JREs existentes nas estações de trabalho. No caso do Windows, as JREs podem
estar localizadas em:
•• C:\Program Files\Java\jre7\lib\ext;
•• C:\Program Files\Java\jdk1.7.0_XX\jre\lib\ext.
O comando javap executado em linha de comando testa a instalação do driver JDBC. Observe
a imagem a seguir:
Com o framework Java SQL é possível ter diversos drivers de banco de dados. Todos os drivers
possíveis são carregados por DriverManager, o qual, na sequência, solicita que cada driver
tente estabelecer a conexão com a URL de destino.
Uma instância da classe Driver deve ser criada e registrada a partir do DriverManager sempre
que essa classe for carregada. A sintaxe obrigatória a todas as versões JDBC anteriores à
versão 4 para realizar essa tarefa é a seguinte:
Class.forName(“com.mysql.jdbc.Driver”)
Para cada banco de dados existe um driver correspondente, o qual é fornecido pelo próprio
fabricante do banco. As sintaxes a seguir são utilizadas para carregar o driver de acesso ao
banco de dados, que é o primeiro passo para importar o pacote java.sql para as classes:
// ODBC
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
// MySQL
Class.forName(“com.mysql.jdbc.Driver”);
// PostgreSQL
Class.forName(“org.postgresql.Driver”);
// DB2
Class.forName(“COM.ibm.db2.jdbc.app.DB2Driver”);
Nessas sintaxes, o modelo de driver ODBC refere-se àquele que fará a conexão com o banco
de dados. A forma adequada de utilizar o driver pode ser consultada na documentação do
fabricante do banco de dados.
try {
Class.forName(“com.mysql.jdbc.Driver”);
} catch (ClassNotFoundException e) {
System.out.println(“Não foi possível carregar o driver.”);
}
4.3.2. Abrindo a conexão
Agora que o driver já foi carregado, você pode estabelecer a conexão com o banco de dados
de sua preferência. Utilize a seguinte sintaxe para obter um objeto de conexão com o Banco
de Dados:
Nessa sintaxe, a classe DriverManager, que será estudada mais adiante, é a responsável pela
conexão com o banco de dados.
Java Programmer – Módulo III (online)
92
De uma forma geral, colocamos todas as instruções de acesso dentro do bloco try, e o seu
respectivo catch possui tratamento para esta exceção:
try {
Connection con = DriverManager.getConnection(
“url”,”myLogin”, “myPassword”);
} catch (SQLException e) {
// realiza o tratamento em caso de falha
}
4.3.3. Interface Connection
A conexão com o banco de dados é representada pela interface Connection. Quando as
instruções SQL são executadas, o resultado é retornado no contexto de uma conexão. Ao utilizar
o método getMetaData, diversas informações são fornecidas a partir do objeto Connection
do banco de dados. Dentre essas informações, estão aquelas que descrevem a tabela do banco
de dados, além das que citam as capacidades da conexão e as stored procedures utilizadas.
Após cada instrução ser executada em um banco de dados, as alterações são confirmadas. Isso
ocorre porque o objeto Connection está configurado com o modo de confirmação automática,
o qual pode ser desabilitado. Nesse caso, uma vez desabilitado o modo automático, torna-
se necessário chamar o método commit explicitamente para que as alterações realizadas no
banco de dados sejam confirmadas.
Para criar um novo objeto Connection, a forma mais comum é utilizar o método getConnection(),
estático, na classe DriverManager.
JDBC
93
4.3.4. Classe DriverManager
Para gerenciar um conjunto de drivers JDBC, utilize o DriverManager, que é uma classe de
Java. A conexão com a fonte de dados é realizada a partir da interface DataSource, sendo que
o principal meio de conexão utilizado é com o objeto DataSource.
Quando o método getConnection for chamado, o DriverManager fará uma pesquisa entre
todos os drivers carregados tanto na sua inicialização quanto explicitamente, em busca do
driver adequado. Para tanto, o mesmo classloader da aplicação ou do applet atual será utilizado.
Antes de executar o exemplo, é necessário criar um banco de dados e uma tabela. Para isso,
execute o seguinte procedimento:
•• Campo: codigo, tipo INT, Primary Key (PK), Not Null, default 1;
4.3.6. Método Close
Sempre que abrir uma conexão com o banco de dados, você deve fechá-la, pois uma conexão
não pode permanecer aberta por um longo período. Para fechar essa conexão, utilize um
método close() da classe Connection.
O método close() pode lançar erros e, por esse motivo, o bloco try/catch deve ser utilizado
para fechar a conexão com o banco de dados. Com relação à chamada a esse método,
recomendamos que ela seja colocada dentro do bloco finally.
A partir do Java 7, é possível utilizar o try-with-resources (try com recursos) para inicializar os
objetos Connection, Statement e Resultset. Para tanto, basta declará-los dentro do comando
try, que se encarrega de fechar os recursos automaticamente ao final do bloco. Veja um
exemplo de como esse recurso pode ser usado com conexões de banco de dados:
Cada operação na base de dados é definida por uma instrução na linguagem SQL, chamadas
de “statements”. São exemplos de statements:
Para realizar tais operações na base de dados, contamos com três diferentes interfaces. Cada
qual com sua característica:
Interface Descrição
Observe que, a partir da conexão que já está aberta, executamos o método createStatement()
para obter um statement, a partir do qual realizamos a operação desejada.
4.5. Operações parametrizadas
Uma instrução SQL parametrizada é um statement em que alguns valores são dinamicamente
assinalados pela aplicação.
PreparedStatement ps = cn.prepareStatement(
“INSERT INTO tab VALUES (?, ?, ?, ?, ?, ?)”);
O método set a ser usado em cada parâmetro depende do tipo de informação a ser preenchida:
NULL setNull()
4.6. Transações
Chamamos de transação um conjunto de operações atômicas realizadas na base de dados e
que podem ser desfeitas em situações de falha ou outro problema.
Quando corretamente utilizada, uma transação garante a integridade dos dados contidos na
base.
Chamamos de rollback o comando utilizado para desfazer as operações retidas pela transação
e de commit o comando utilizado para efetivá-las na base de dados.
O JDBC permite a criação de aplicações Java que manipulam transações com bancos de dados
que oferecem suporte a este tipo de recurso. Para isso, contamos com os seguintes métodos
da interface java.sql.Connection:
•• setAutoCommit(boolean);
•• commit();
•• rollback().
Java Programmer – Módulo III (online)
102
4.7. Consultas
As consultas na base de dados são realizadas mediante o comando SELECT. Através dele,
podemos obter os dados contidos em uma ou mais tabelas, seguindo critérios, agrupamentos
e/ou ordenações, conforme necessidade da aplicação.
Para capturarmos os dados provenientes do comando SELECT, o JDBC conta com a interface
java.sql.ResultSet.
Após executar a consulta através deste comando, deve-se mover o cursor para a primeira linha
do ResultSet e, se esta linha existir, podemos coletar os seus dados e avançar para a próxima
linha.
Para avançar o cursor para a próxima linha, utilize o método next(). Além de forçar o cursor a
avançar, este método retorna um booleano informando se a próxima linha existe ou não:
Tendo avançado para uma linha válida, podemos então coletar os dados de algum campo
daquele registro através de um método get, conforme o tipo do campo:
No exemplo a seguir, considere que a tabela TAB_FUNC possua os campos NOME e SALARIO
respectivamente dos tipos varchar e number (com parte fracionária):
4.8. Pacote javax.sql
Este pacote fornece a API para acesso à fonte de dados no lado do servidor e processamento
de dados da linguagem Java. Além disso, ele complementa o pacote java.sql. Desde a versão
1.4, vem incluso na versão Java Standard Edition (Java SE), e é uma parte essencial da versão
Java Enterprise Edition (Java EE).
Para executar um código que usa APIs presentes desde a versão 1.6, use
um driver de tecnologia JDBC que implementa a API JDBC 4.0 ou superior.
Sempre verifique a documentação do driver para ter certeza de que ele
implementa os recursos específicos que você deseja usar.
JDBC
105
•• Transações distribuídas;
•• A interface Rowset.
Os aplicativos usam DataSource e APIs RowSet diretamente, mas o pool de conexão e APIs de
transação distribuída são usados internamente pela infraestrutura de camada intermediária.
4.8.1. DataSource
Apesar da classe DriverManager, mecanismo original da API, ainda ser válida, e o código
usado continuar a executá-la, o pacote javax.sql fornece o modo preferido para efetuar uma
conexão com uma fonte de dados: o mecanismo DataSource, que oferece muitas vantagens
sobre o mecanismo DriverManager.
•• Você pode alterar as propriedades de uma fonte de dados, sem que seja necessário
proceder a alterações no código de aplicação quando algo na fonte de dados ou no driver
for alterado;
A API Java Naming and Directory Interface (JNDI) é usada por serviços de identificação para
registrar nomes lógicos para fontes de dados. Os administradores de sistemas fazem isso
para que um aplicativo possa recuperar o objeto DataSource por meio de uma pesquisa sobre
o nome lógico registrado. Além disso, o aplicativo também permite usar o objeto DataSource
para criar uma conexão com a fonte de dados física que ele representa.
Java Programmer – Módulo III (online)
106
Você pode implementar um objeto DataSource para trabalhar com a infraestrutura de camada
intermediária, fazendo com que as conexões que produz sejam agrupadas para reutilização.
Um aplicativo que usa essa implementação DataSource recebe automaticamente uma conexão
que participa no pool de conexão. Você também pode fazer com que um objeto DataSource
funcione com a infraestrutura da camada intermediária, assim. as conexões que produz podem
ser usadas para transações distribuídas sem qualquer codificação especial.
O pool de conexão é totalmente transparente. Isto significa que ele é feito automaticamente
na camada intermediária de uma configuração Java EE, ou seja, do ponto de vista de um
aplicativo, nenhuma mudança no código é necessária. Você pode utilizar o método DataSource.
getConnection para obter o pool de conexão e usá-lo da mesma forma que usa qualquer
objeto Connection.
JDBC
107
•• ConnectionPoolDataSource;
•• PooledConnection;
•• ConnectionEvent;
•• ConnectionEventListener;
•• StatementEvent;
•• StatementEventListener.
As classes e interfaces listadas anteriormente são usadas pelo gerenciador de pool de conexão,
que é uma facilidade da camada intermediária. Ao chamar o objeto ConnectionPoolDataSource
para criar um objeto PooledConnection, o novo objeto PooledConnection será registrado
pelo gerenciador como um objeto ConnectionEventListener. Se o gerenciador for um listener
e a conexão for fechada ou houver um erro, ele receberá uma notificação, que inclui um objeto
ConnectionEvent.
4.8.3. ConnectionPoolDataSource
Esta interface produz objetos PooledConnection. Um objeto que implementa esta interface
normalmente será registrado com um serviço de identificação que se baseia na Java Naming
and Directory Interface (JNDI).
•• getPooledConnection()
Este método tenta estabelecer uma conexão de base de dados física, a qual pode ser usada
como uma conexão do pool. Sua sintaxe completa é:
PooledConnection getPooledConnection()
throws SQLException
Este método tenta estabelecer uma conexão de base de dados física, a qual pode ser usada
como uma conexão do pool. Sua sintaxe completa é:
PooledConnection getPooledConnection(String user, String password)
throws SQLException
4.8.3.1. PooledConnection
•• getConnection()
Este método cria e retorna um objeto Connection, que é um identificador para a conexão física
representada pelo objeto PooledConnection. Este método é chamado pelo gerenciador de
pool de conexão quando o método DataSource.getConnection é chamado por um aplicativo
e não há objetos PooledConnection disponíveis. Sua sintaxe completa é:
Connection getConnection()
throws SQLException
•• close()
Este método fecha a conexão física que o objeto PooledConnection representa. Um aplicativo
nunca chama esse método diretamente, ele é chamado pelo gerenciador de pool de conexão.
Sua sintaxe completa é:
void close()
throws SQLException
•• addConnectionEventListener(ConnectionEventListener listener)
Este método registra um determinado objeto ouvinte de eventos (listener) para que seja
notificado quando um evento ocorrer no objeto PooledConnection. Sua sintaxe completa é:
void addConnectionEventListener(ConnectionEventListener listener)
Java Programmer – Módulo III (online)
110
•• removeConnectionEventListener(ConnectionEventListener listener)
void removeConnectionEventListener(ConnectionEventListener listener)
•• addStatementEventListener(StatementEventListener listener)
void addStatementEventListener(StatementEventListener listener)
•• removeStatementEventListener(StatementEventListener listener)
void removeStatementEventListener(StatementEventListener listener)
4.8.4. Transações distribuídas
Assim como as conexões do pool, as conexões feitas por um objeto DataSource podem
participar de transações distribuídas. Isso significa que um aplicativo é capaz de envolver
fontes de dados de vários servidores em uma única transação.
•• XADataSource;
•• XAConnection.
Essas interfaces são usadas pelo gerenciador de transação, isto significa que elas não são
usadas por um aplicativo diretamente.
Para participar de uma transação distribuída, um aplicativo simplesmente cria conexões para
fontes de dados que pretende utilizar através do método DataSource.getConnection, como
faz normalmente. O gerenciador de transações gerencia a operação nos bastidores, o que
significa que a interface XADataSource cria objetos XAConnection, e cada um deles cria um
objeto XAResource que será utilizado para gerenciar a conexão.
4.8.5. RowSet
A interface RowSet trabalha com várias outras classes e interfaces, que podem ser agrupadas
em três categorias, as quais veremos adiante.
Você pode implementar a interface RowSet de várias maneiras, isto significa que é possível
ser criativo e fazer surgir novas maneiras de usar conjuntos de linhas.
•• Notificação de eventos
•• RowSetListener
Ao mudar uma de suas linhas, alterar o que está alinhado ou mover o cursor, o objeto RowSet
notifica cada listener que possui registrado. A dinâmica entre objeto de evento e listener segue
a mesma lógica já vista em outras áreas em que aplicamos objetos ouvintes de eventos.
•• RowSetEvent
Como parte de seu processo interno de notificação, um objeto RowSet cria uma instância
de RowSetEvent e passa para o listener, o qual pode usar esse objeto RowSetEvent para
descobrir em qual conjunto de linhas ocorreu o evento.
Java Programmer – Módulo III (online)
114
•• Metadata
•• RowSetMetaData
A interface RowSetInternal é implementada por um objeto RowSet que pode chamar o objeto
RowSetReader associado a ele para que seja preenchido com os dados. Além disso, este
objeto RowSet pode chamar o objeto RowSetWriter associado a ele para escrever quaisquer
alterações das suas linhas na fonte de dados que originou essas linhas. Quando um conjunto
de linhas permanece conectado à sua fonte de dados, não é preciso usar um reader e writer,
porque é possível operar na fonte de dados diretamente.
•• RowSetInternal
•• RowSetReader
•• RowSetWriter
☐☐ a) close()
☐☐ b) stop()
☐☐ c) break()
☐☐ d) dbend()
☐☐ a) ConnectionPoolDataSource
☐☐ b) PooledConnection
☐☐ c) ConnectionEvent
☐☐ d) PooledConnectionEvent
☐☐ e) ConnectionEventListener
JDBC
121
☐☐ a) DataSource, indiretas.
☐☐ b) DataSource, distribuídas.
☐☐ c) Connection, indiretas.
☐☐ d) Connection, distribuídas.
☐☐ b) Por meio dos drivers, você pode realizar tarefas como a criação
de conexões e tabelas, a chamada de Stored Procedures e a
execução de comandos SQL.
5.1. Introdução
O Garbage Collector é um recurso que oferece uma solução automatizada para o gerenciamento
de memória, elemento de extrema importância em determinados tipos de aplicativos.
Normalmente, os programas realizam a leitura dos dados e executam algumas tarefas com
eles, gravando-os, logo em seguida, em um banco de dados. Assim que são gravados, os
dados presentes no conjunto de memória há mais tempo devem ser eliminados ou, ainda,
devem ser excluídos e posteriormente recriados.
Você deve recriar o conjunto em questão antes que o lote de dados seguinte seja processado.
Em linguagens de programação que não contam com o Garbage Collector, como C e C++,
o processo de eliminação dos dados mais antigos não é realizado automaticamente, o que
possibilita a recuperação inapropriada de partes da memória ou, ainda, sua perda definitiva
em virtude de falhas na lógica para a exclusão de dados de um conjunto.
Quando ocorrem repetidas vezes, os vazamentos de memória, que são perdas de pequenas
partes da memória, podem tornar certa quantidade de memória inacessível e, assim, causar a
paralisação de programas.
Você pode criar um código capaz de realizar o gerenciamento da memória de forma manual,
porém, além de ser uma tarefa complexa, pode aumentar de forma considerável os esforços
para o desenvolvimento de um programa. O Garbage Collector da linguagem Java permite
que esse gerenciamento de memória seja realizado de forma automática.
Esse recurso realiza o gerenciamento da memória de forma automática, ou seja, ele efetua
diversas tarefas com a finalidade de desalocar da memória principal quaisquer recursos que,
porventura, não estejam sendo utilizados.
Garbage Collector
125
Vale destacar que a memória pode ser utilizada de diferentes formas, todas as vezes que um
programa é executado. Você pode usá-la, por exemplo, para criar uma pilha ou um heap em
áreas de método.
É preciso eliminar quaisquer objetos que não possam ser alcançados pelo programa em
execução, garantindo, deste modo, que o heap tenha a maior disponibilidade de espaço
possível. Assim que ocorrer a execução do Garbage Collector, os objetos que não podem ser
alcançados, ou seja, que não possuem uma referência válida em execução no programa, serão
eliminados.
Uma vez que um programa Java permanece em um ciclo constante de criação de novos objetos
e eliminação daqueles que não são mais utilizados, o Garbage Collector é o responsável por
localizar e excluir da memória os objetos que não são mais utilizados, a fim de permitir a
continuidade desse ciclo.
Você não deve se preocupar com a alocação de dados inadequados porque, com o Garbage
Collector, quaisquer objetos que perdem sua referência ou que saem de seu escopo são
coletados automaticamente.
O Garbage Collector é executado em segundo plano e sobre uma determinada thread. Durante
a execução, ele realiza uma análise da memória e verifica a quais referências os objetos estão
vinculados.
Java Programmer – Módulo III (online)
126
Assim que é iniciado um novo ciclo do Garbage Collector, os objetos que estiverem sem
referências são coletados para que, dessa forma, sejam desalocados e liberem os recursos
utilizados.
Uma das maneiras de solicitar a execução do Garbage Collector é por meio da chamada ao
método estático System.gc(). Observe o exemplo a seguir para melhor compreender como
esse recurso é executado – o que ocorre em duas fases distintas:
•• Na primeira fase, após a execução da segunda linha, o objeto Cliente poderá ser varrido
da memória pelo Garbage Collector, visto que ele virou lixo;
•• Na segunda fase, é executado o método finalize() para os objetos que não possuem
referências. Este método possui a seguinte assinatura: void finalize() throws Throwable.
Os objetos presentes no objeto que está sendo varrido são liberados e, posteriormente, o
Garbage Collector faz uma nova verificação, a fim de encontrar objetos sem referência ou,
ainda, que não estão sendo utilizados.
O Java chama o método finalize() antes de chamar o Garbage Collector, a fim de permitir
que as tarefas sejam executadas anteriormente à liberação da memória. Utilize este método
em situações específicas, como quando desejar salvar um determinado arquivo, antes que um
objeto seja liberado.
Quando você trabalha com a linguagem Java, o método finalize() também pode ser adicionado
à classe. Vale destacar que não é possível saber o momento em que esse método será chamado.
Por isso você não deve confiar em finalize() para reciclar recursos presentes em pequenos
armazenamentos.
Lembre-se que o método finalize() é chamado uma única vez pelo Garbage Collector. Além
disso, quando esse método é chamado, um objeto pode ser salvo da exclusão.
O código que você pode inserir em um método comum também pode ser inserido em finalize().
Isso significa que é possível inserir código que retorne a referência de um objeto para outro,
o que resultaria na desqualificação desse objeto para coleta. Mesmo que posteriormente esse
objeto volte a ser qualificado para coleta, o Garbage Collector poderá excluí-lo. Nesse caso,
porém, o método finalize() já foi executado para o objeto em questão.
Java Programmer – Módulo III (online)
128
Ainda, no decorrer deste tópico, você aprenderá como forçar a execução do Garbage Collector
quando necessário e como limpar os objetos antes que sejam removidos da memória.
O que este código demonstra é a configuração da variável de referência que aponta ao objeto
com null, neste caso, a variável teste. Além disso, o código também realiza o seguinte:
Garbage Collector
129
•• Na linha 5, o objeto StringBuffer, cujo valor é Java, tem a variável de referência teste
atribuída a ele;
Assim que a execução do método é finalizada, todos os objetos que foram criados nesse
método estão qualificados para a coleta. Isto significa que as variáveis locais existirão apenas
durante a execução do método.
Java Programmer – Módulo III (online)
130
Porém, um objeto não ficará disponível para o Garbage Collector se o método devolver uma
referência a esse objeto, e essa referência for armazenada por uma variável fora do método.
Essa situação será demonstrada no código descrito a seguir:
O método getDate() cria os objetos Date e StringBuffer, sendo que ambos contêm as
informações a respeito da data. Porém, como o objeto Date é retornado, ele está desqualificado
para a coleta, ao contrário de StringBuffer. Este último está qualificado, independentemente
do fato de a variável agora ter sido configurada com null de forma explícita.
Em razão da quantidade de objetos que possui, uma ilha de isolamento pode se tornar
consideravelmente grande. O código a seguir apresenta um exemplo deste tipo de ilha:
O código anteriormente descrito apresenta três objetos qualificados para a coleta, na seguinte
situação:
•• Três objetos Dado, d2, d3 e d4, com variáveis de instância que os fazem apontar uns para
os outros;
Para que você compreenda melhor as ilhas de isolamento, considere uma classe cuja variável
de instância tem a função de ser a variável de referência de outra instância pertencente à
mesma classe.
Java Programmer – Módulo III (online)
132
Caso existam duas dessas instâncias fazendo referência uma à outra, os objetos continuarão
com uma referência válida cada, mesmo que suas outras referências sejam removidas. Porém,
nenhuma thread ativa poderá acessar esses objetos. Essas ilhas de objetos, então, tornam-se
isoladas e, assim que o Garbage Collector for executado, elas serão excluídas.
A Runtime contém um único objeto para cada um dos programas principais, padrão esse
conhecido como Singleton, o qual tem como função fornecer um mecanismo que permite
o estabelecimento de uma comunicação direta com a JVM. O objeto Singleton é retornado
quando você utiliza o método Runtime.getRuntime(), com a finalidade de capturar a instância
do método Runtime.
Você pode, ainda, obter o objeto Singleton por meio dos métodos estáticos presentes na
classe System, como o System.gc() – o qual, como você viu anteriormente, permite solicitar
que a coleta seja realizada. Contudo, chamar este método não assegura a liberação de espaço
suficiente na memória de forma que seja possível desconsiderar a execução do Garbage
Collector.
O System.gc() pode liberar tanto espaço em memória quanto possível, mas isso depende da
JVM utilizada, uma vez que esta máquina pode não ter implementado essa rotina. Além disso,
o System.gc() não garante uma grande liberação de memória porque é possível que, após a
coleta ter sido executada, uma outra thread execute uma grande alocação de memória.
Ao verificar o resultado da execução desse programa, observe que a JVM realizou a coleta dos
objetos qualificados para tal. Lembre-se que diferentes resultados podem ocorrer quando o
Garbage Collector é chamado, isto significa que não há garantias de que os objetos qualificados
serão de fato removidos. No caso específico do programa apresentado anteriormente, o
Garbage Collector foi chamado no momento em que não havia operações sendo executadas.
O Garbage Collector será seguramente executado nas situações em que o espaço disponível
na memória estiver bastante reduzido. Essa execução ocorrerá anteriormente ao lançamento
de uma exceção OutOfMemoryException.
5.9.1. Created
Created (criado) é o primeiro estado pelo qual passa um objeto. Neste estado, as seguintes
tarefas são realizadas:
A realização de todas essas operações tem um custo, que varia de acordo com duas
implementações: a da JVM e a da classe construída.
5.9.2. In use
O segundo estado pelo qual o objeto criado passa é o in use (em uso), desde que esse objeto
esteja ligado a uma variável. Para que um objeto seja considerado em uso, é preciso que ele
possua, pelo menos, uma referência strong.
5.9.3. Invisible
Um objeto passa pelo estado invisible (invisível) quando não há referências strong que possam
ser acessadas pelo programa, embora seja possível que ainda existam outras referências.
Você deve saber que nem todos os objetos passam pelo estado invisible.
Java Programmer – Módulo III (online)
136
•• No final deste bloco, não há uma sintaxe que possibilite o acesso a objeto por parte do
programa. Assim, parece que a variável teste foi excluída da pilha, tornando seu objeto
associado inacessível. Para que isso não ocorra, é necessária uma implementação eficiente
da JVM, pois ela não permite que a referência seja zerada quando ela está fora do escopo;
•• Até que o método executar retorne, a referência do objeto continua sendo do tipo strong.
Por não haver coleta de objetos invisíveis, alguns vazamentos de memória podem ser
causados. Neste caso, é preciso anular as referências de forma explícita para que o Garbage
Collector seja executado.
5.9.4. Unreachable
Outro estado pelo qual um objeto passa é unreachable (inalcançável). Isto acontece quando
o objeto não possui qualquer referência do tipo strong. Contudo, nem toda referência strong
é capaz de fazer com que o objeto seja mantido na memória. Essa referência deve vir de
variáveis estáticas, de variáveis temporárias na pilha e, também, de referências especiais de
um código JNI nativo.
Garbage Collector
137
Objetos que estão no estado unreachable estão qualificados para coleta. Porém, isto não
significa que eles serão coletados de forma instantânea, uma vez que a Java Virtual Machine
pode adiar tal coleta para aguardar até que haja necessidade do espaço da memória utilizado
pelo objeto.
5.9.5. Collected
Os objetos que estão no estado collected (coletado) são aqueles identificados como
inalcançáveis pelo Garbage Collector e que foram preparados para o processamento realizado
antes de sua desalocação. Isso significa que, se há um método finalize() nesse processo, o
objeto é marcado para a finalização, porém, caso contrário, o objeto passa para outro estado,
o finalized (finalizado) – o que será abordado posteriormente.
Quando um finalizador é definido por uma classe, ele deve ser chamado por toda a instância
referente a ela, anteriormente à desalocação. Isso causa um atraso nesse processo – em razão,
exatamente, da inclusão do finalizador.
5.9.6. Finalized
Os objetos estão no estado finalized (finalizado) quando permanecem no estado inalcançável
após a execução de seu método finalize(), caso eles contenham esse método. A execução do
finalizador está sob o controle da Java Virtual Machine.
Objetos em estado finalizado aguardam para serem desalocados. Você pode prolongar o tempo
de vida desse objeto ao adicionar um finalizador. Portanto, não é aconselhável adicionar um
finalizador em objetos cujo tempo de vida deve ser curto.
5.9.7. Deallocated
O último estado pelo qual um objeto passa no processo do Garbage Collector é o deallocated
(desalocado). Objetos qualificados para a desalocação são aqueles que, ao chegarem neste
ponto, ainda permanecem inalcançáveis.
A JVM controla o momento em que a desalocação do objeto é realizada de fato, bem como a
forma como essa desalocação acontece.
5.10. Objetos de referência
O pacote java.lang.ref tem a finalidade de determinar as classes de objetos de referência que
permitem certo grau de interação com o Garbage Collector.
Você deve utilizar esses objetos de referência para manter uma referência a outro objeto,
de forma que o Garbage Collector ainda possa reclamá-lo. Dentre esses objetos, temos
SoftReference, WeakReference e PhantomReference.
Os programas trabalham com as referências strong por padrão. Isto é feito para evitar a
eliminação de objetos, uma vez que um objeto alcançável com esse tipo de referência é capaz
de manter-se referenciado na memória.
Esses tipos de referência foram descritos do mais forte para o mais fraco, ou seja, a referência
soft é mais forte que a referência weak, que, por sua vez, é mais forte que a referência
phantom. O ciclo de vida de um objeto corresponde a esses diferentes níveis de alcance,
desde o mais forte até o mais fraco. Veja como:
Destacamos, ainda, que há objetos que são inalcançáveis e, por isso, não podem ser reclamados
pelos métodos já apresentados.
5
Teste seus conhecimentos
Garbage Collector
Java Programmer – Módulo III (online)
142
☐☐ a) Nuvem privada
☐☐ b) Nuvem pública
☐☐ c) Nuvem híbrida
☐☐ d) Nuvem comunitária
1.1. Introdução
Com o Java, podemos criar aplicações que interagem com o usuário através de elementos
como caixas de diálogos e janelas, que podem conter botões, caixas de texto, ícones, menus,
e outros elementos interativos. De uma forma geral, chamamos a estes componentes de
interface gráfica, cujo principal objetivo é tornar a sua aplicação amigável e de fácil utilização
pelo usuário.
A elaboração de interfaces gráficas com Java vem sofrendo uma série de inovações à medida que
a linguagem evolui. Diversas bibliotecas já foram criadas e modificadas visando à construção
de tais aplicações por programadores Java. As bibliotecas mais conhecidas são: AWT, Swing e
SWT.
O JavaFX é a mais recente biblioteca elaborada para a construção de aplicações gráficas. Suas
principais características são:
Observe:
JavaFX
147
Um arquivo FXML contém posição, tamanho, aparência, cor e todas as outras características
de cada componente utilizado em uma tela.
Veja um exemplo:
Java Programmer – Módulo II (online)
148
1.2. Scene Builder
Embora seja possível criar e editar interfaces gráficas FXML com qualquer editor de texto, a
Oracle dispõe de uma ferramenta IDE especificamente criada para isso: o Scene Builder.
Observe:
Através dele, podemos elaborar interfaces ricas utilizando todos os recursos do JavaFX, onde
podemos manipular tamanho e posição dos componentes com o clique e arraste e alterar as
suas características pelo painel de propriedades.
http://www.oracle.com/technetwork/java/javase/downloads/index.html
JavaFX
149
Ao salvar a sua interface gráfica com o Scene Builder, será gerado um arquivo FXML que
poderá ser posteriormente editado pela própria ferramenta ou em algum editor de texto.
Após gerado, o arquivo .fxml deverá ser incorporado à sua aplicação Java:
Em seguida, poderá ser renderizado pela biblioteca JavaFX, através da classe FXMLLoader:
Java Programmer – Módulo II (online)
150
1.3. Principais componentes
Cada componente de uma janela, inclusive a própria janela, é representado por uma classe da
biblioteca JavaFX. A seguir, veremos os principais componentes visuais desta biblioteca:
JavaFX
151
1.3.1. Stage
Todos os componentes visuais do JavaFX são renderizados em uma região denominada
Window. A classe javafx.stage.Window trata-se de uma abstração que possui diferentes
implementações, em conformidade com o ambiente/sistema operacional em que a aplicação
está sendo executada.
Propriedade/
Descrição
Método
A classe Stage pode ser normalmente instanciada para a exibição de diversas janelas de
sua aplicação. No entanto, a primeira janela a ser aberta (janela principal) não poderá ser
instanciada diretamente.
Para executar esta classe, utilize sempre o método lauch(), conforme exemplo anterior.
1.3.2. Scene
A classe javafx.scene.Scene representa a película de fundo da região em que os elementos
gráficos serão exibidos. Através do método setFill(), podemos definir uma cor, foto ou efeito
de fundo para sua janela. Este método aceita os seguintes tipos como argumentos:
Tipo Descrição
1.3.3. Pane
javafx.scene.layout.Pane é a classe base para os diversos tipos de painéis da biblioteca JavaFX.
Utilizamos painéis para especificar o posicionamento dos demais componentes em tela, de
forma a se reorganizarem conforme o tamanho da janela.
Segue adiante uma breve descrição dos principais painéis, todos pertencentes ao pacote
javafx.scene.layout:
•• Pane: Principal tipo de painel, cujos elementos internos possuem tamanho e posição fixa:
•• AnchorPane: Permite criar elementos que aderem a um dos quatro cantos da tela:
JavaFX
155
•• GridPane: Divide a tela em uma região tabular com a quantidade desejada de linhas e
colunas, formando um grid onde todos os elementos são uniformemente distribuídos
quando a janela é redimensionada:
Java Programmer – Módulo II (online)
156
Cada um destes painéis também funciona como um componente a ser inserido no painel raiz,
contendo as suas propriedades isoladamente. Em outras palavras, é possível aninhar painéis
de tipos diferentes combinando-os para formar layouts mais sofisticados.
1.3.4. Button
Um componente Button representa um botão em que o usuário pode clicar ou pressionar a
barra de espaço para que algum evento seja disparado.
JavaFX
157
•• Text: Define o rótulo a ser exibido pelo botão. Utilize o caractere “_” (undeline) para definir
a tecla de acesso. O underline deve preceder a letra desejada e a propriedade Mnemonic
Parsing precisa estar habilitada;
1.3.5. TextField
Este componente representa uma caixa de texto em que o usuário poderá digitar. Observe:
Sua principal propriedade é a Text, que permite definir um texto inicial a ser exibido pela
caixa. Utilize os métodos getText() e setText() para manipular programaticamente o conteúdo
digitado pelo usuário.
1.3.6. Label
Um rótulo utilizado na exibição de mensagens fixas em sua janela:
Java Programmer – Módulo II (online)
158
1.3.7. ImageView
Permite adicionar uma imagem ou ícone em uma posição específica. Veja um exemplo:
1.3.8. CheckBox
Componente que pode ser marcado ou desmarcado pelo usuário:
Utilize a propriedade Selected para que a Checkbox seja inicialmente exibida como marcada.
Utilize os métodos isSelected() e setSelected() para verificar e assinalar programaticamente
este componente como selecionado (checado).
JavaFX
159
1.3.9. RadioButton
Possui a mesma funcionalidade de uma CheckBox, porém, pode ser utilizado em grupos em
que podemos ter a seleção exclusiva. Observe:
Para ter o efeito de seleção exclusiva, no Scene Builder, selecione todos os componentes de
um mesmo grupo e digite um nome na propriedade Toggle Group:
Java Programmer – Módulo II (online)
160
1.4. Manipulação de eventos
A manipulação de eventos em sua janela é realizada por uma classe denominada controladora.
Através desta classe, podemos controlar dinamicamente o comportamento, aparência e estado
de seus componentes.
1.4.1. Identificadores
Cada componente a ser controlado dinamicamente deve possuir um identificador único dentro
do arquivo FXML. Para assinalar o id (identificador) de um componente, basta selecionar o
componente desejado e, na seção Code do Inspector, digite o id desejado na caixa fx:id:
1.4.2. Eventos
Os eventos são as ações realizadas sobre os componentes de uma interface gráfica, como um
clique, pressionar de tecla, fechamento de janela, etc.
Ao selecionar um componente de sua interface gráfica, o Scene Builder exibe a lista de eventos
associados no painel Code.
JavaFX
161
Evento Descrição
On Mouse
Executado ao clicar sobre o componente.
Clicked
On Mouse
Executado ao passar o ponteiro sobre o componente.
Entered
On Mouse
Executado ao sair com o ponteiro de cima do componente.
Exited
On Key Executado quando o usuário libera a tecla que foi baixada sobre
Released o componente.
Java Programmer – Módulo II (online)
162
Para manipular um evento de algum dos elementos de sua interface gráfica, selecione o
componente e, na seção Code do Inspector, procure pelo evento desejado. Digite um nome
de identificador para este evento:
Embora o Scene Builder não permita a elaboração de código Java diretamente, ele fornece
uma maneira simplificada de desenvolver a classe controladora para a interface gráfica que
está sendo editada.
4. No painel Document (lado inferior esquerdo do Scene Builder), selecione a aba Controller
e preencha o campo Controller class com o nome da classe controladora a ser criada. Este
deve ser o nome completo da classe, incluindo o package ao qual ela irá pertencer:
6. Crie uma classe com o mesmo nome e package especificados e cole o template gerado pelo
Scene Builder:
Desta forma, podemos perceber o total isolamento entre a interface gráfica (arquivos FXML) e
as regras de negócios (chamadas a partir da classe controladora).
Utilizando estes conceitos, podemos desenvolver diversos tipos de aplicações para ambiente
desktop, como aplicações cliente/servidor com acesso à base de dados, inteiramente baseadas
no JavaFX.