
Atenção: por essa edição ser muito antiga não há arquivo PDF para download.Os artigos dessa edição estão disponíveis somente através do formato HTML.
Pente fino
New I/O Fundamental
Parte 2: Operações Avançadas
Arquivos mapeados em memória, I/O não
No artigo anterior apresentamos os conceitos básicos do NIO e algumas novas operações oferecidas por essa API, como a transferência direta entre buffers e buffers diretos. Neste segundo artigo, apresentaremos recursos avançados da API New I/O que colocam a linguagem Java em pé de igualdade com linguagens de mais baixo nível como C/C++, na área de manipulação de I/O: mapeamento de arquivos em memória, file locking, I/O não
Mapeamento de arquivo em memória
Quando um arquivo é aberto por um processo, o sistema operacional (SO) cria um buffer na memória virtual para a transferência de dados entre o processo e o arquivo. Ou seja, em vez de escrever diretamente no arquivo, o processo escreve em um buffer e o SO, de tempos em tempos, transfere o conteúdo do buffer para o arquivo (a leitura é feita de forma similar, usando também um buffer). Embora seja um procedimento transparente ao programador, a transferência para buffers intermediários penaliza a performance em algumas situações, principalmente quando o arquivo manipulado é muito grande ou acessado por vários processos.
Para resolver esse problema, sistemas operacionais modernos oferecem a possibilidade de mapear um arquivo diretamente em memória (memory
A economia de memória é ainda maior quando vários processos mapeiam o mesmo arquivo. Pelo procedimento tradicional, seria preciso alocar uma região de memória física para cada processo acessando o arquivo. Além disso, as modificações realizadas na memória mapeada são imediatamente refletidas no arquivo e vice
Para mapear um arquivo em memória com NIO, basta chamar map(FileChannel.MapMode modo, long pos, long tamanho)
A aplicação JCanyon (veja links) é um exemplo interessante do uso de NIO – um simulador de vôo que usa o mapeamento direto em memória para ler as imagens de satélite (que ocupam cerca de 200 Mb) e dados topográficos (mais de 100 Mb), além de buffers diretos para o envio de informações à placa de vídeo.
File Locking
Imagine a situação em que vários threads precisem ler e alterar um mesmo arquivo. É preciso um mecanismo que garanta que, enquanto um thread altera o arquivo, os demais não possam acessá
Nessas situações, a saída é delegar ao sistema operacional o controle de acesso ao arquivo, através do mecanismo de file locking, que em NIO é implementado pelos métodos lock() e tryLock() da classe FileChannel. O primeiro método bloqueia o thread atual até que ela obtenha o lock no arquivo (representado pela classe java.nio.channels.FileLock); o segundo não bloqueia o thread, mas retorna null caso não tenha sido possível obter o lock (veja mais sobre o conceito de bloqueio na seção "I/O não bloqueante", a seguir).
Também é possível obter um lock apenas para um trecho de um arquivo, através dos métodos sobrecarregados lock(int posInicial, int tamanho, boolean compartilhado) e tryLock(int posInicial, int tamanho, boolean compartilhado). Aqui, o terceiro parâmetro determina se o lock será compartilhado ou exclusivo – quando um trecho do arquivo é bloqueado em modo compartilhado, esse trecho pode ser bloqueado por outros programas que também adquiram um lock em modo compartilhado, mas não em modo exclusivo. Tipicamente, o lock compartilhado é usado para leitura, e o modo exclusivo, para escrita.
A Listagem 3 mostra um exemplo de como fazer o locking em um arquivo inteiro; a Listagem 4 e a Tabela 1 apresentam o resultado de várias aplicações tentando obter o lock simultaneamente. Note que a API ainda não fornece um método para realizar o locking compartilhado de um arquivo inteiro: é preciso simular esse método chamando lock(0, Long.MAX_VALUE, true).
Por fim, como o mecanismo de file locking é de responsabilidade do SO e não da JVM, é importante salientar alguns pontos:
· Nem todos os SOs suportam locks compartilhados. Para saber se o lock obtido é compartilhado ou não, chame o método isShared() de FileLock;
· Locks podem ser implementados de duas formas pelos SOs: forçados (mandatory) e recomendados (advisory). Quando são recomendados, o SO não proíbe o acesso a um arquivo “locado” – apenas indica a existência do lock, e os processos precisam colaborar entre si para não sobrescrever o arquivo. Assim, para garantir a máxima compatibilidade multiplataforma, é preciso assumir que todos os locks são recomendados, e não obrigatórios;
· Alguns SOs não permitem o lock de arquivos mapeados em memória;
· SOs geralmente garantem locks a processos, e não a threads individuais. Dessa forma, locks adquiridos valem para a JVM como um todo, e não para cada thread. Ou seja, o mecanismo de file locking deve ser usado apenas para restringir o acesso a arquivos entre aplicações sendo executadas em JVMs diferentes; para threads na mesma JVM, o acesso ao arquivo deve ser controlado por blocos synchronized.
I/O não
Quando uma operação de I/O bloqueante é chamada, o controle de execução só é passado de volta à thread que a chamou quando a operação terminar ou falhar: a execução da thread fica bloqueada enquanto o método não retorna. Esse comportamento é muitas vezes indesejável, pois em vez de estar bloqueada esperando por uma operação de I/O, a thread poderia estar ativa executando outras tarefas.
Já quando a operação é não
Tomemos como exemplo o método connect() da classe SocketChannel. Quando em modo bloqueante – o modo é definido através do método configureBlocking(boolean modo) – a chamada a esse método é bloqueada até que a conexão seja estabelecida ou um erro de I/O ocorra (por exemplo, timeout na conexão). Já em modo não