Você está na página 1de 27

Figura 7-2.

Os processos de aplicação podem ser divididos em dois, tendo um ou mais


os subprocessos realizam um trabalho útil.

Embora a programação de multiprocessos funcione bem, há desvantagens em seu uso. Primeiro, quando um
processo se divide em dois, há sobreposição entre o armazenamento de dados de um processo e de outro. Como
duas cópias dos dados estão sendo mantidas, mais memória do que o necessário é consumida. Em segundo lugar,
não existe uma maneira fácil para um processo acessar e modificar os dados de outro. No Unix, a comunicação
entre processos (IPC) é usada, criando tubos de dados que permitem que um processo se comunique com outro.
No entanto, não é tão fácil projetar um software que compartilhe dados em um ambiente multiprocessado quanto
em um multithread.

7.1.3 Programação Multi-threaded

A programação multithread requer uma maneira diferente de ver o software. Em vez de executar uma série de etapas
sequencialmente, as tarefas são executadas simultaneamente - ou seja, muitas tarefas são executadas ao mesmo tempo, em
vez de uma tarefa ter que terminar antes que outra possa começar. Multi-threading, também conhecido como vários threads
de execução, permite que um programa tenha várias instâncias em execução, enquanto usa o mesmo espaço de memória
compartilhada e código (ao contrário da programação de multiprocessos, que usa espaços de endereço de memória
separados, tornando difícil a comunicação entre os processos) . Um aplicativo pode estar executando muitas tarefas
diferentes simultaneamente, e os threads podem acessar variáveis de dados compartilhados para trabalhar de forma
colaborativa.

Você provavelmente já viu esse tipo de comportamento em aplicativos de software, embora talvez não o tenha
reconhecido. Quando programas com uma interface gráfica de usuário são executados, eles normalmente usam
multi-threading. Por exemplo, um aplicativo GUI em execução em uma máquina Windows pode estar executando
alguma tarefa de processamento, como atualizar células em uma planilha ou enviar um documento para a impressora.
Você já se perguntou por que pode mover uma janela, redimensioná-la ou acessar o menu de arquivo, embora o
aplicativo esteja executando alguma outra tarefa? A interface gráfica do usuário (que geralmente usa componentes
GUI do sistema operacional subjacente) está sendo continuamente atualizada, independente do aplicativo real, por um
ou mais encadeamentos GUI que atualizam a exibição da tela e capturam eventos GUI, como cliques e arrastos do
mouse.

132
A maioria dos sistemas operacionais modernos oferece suporte a multitarefa. Embora alguns aplicativos não
aproveitem as vantagens do multithreading e sejam escritos como aplicativos single-threaded, eles ainda estão
sendo executados em um ambiente multithread. O sistema operacional pode permitir que outros aplicativos e outros
threads sejam executados, enquanto o aplicativo de thread único fica alheio. Então, como isso é feito?

A menos que você tenha mais de uma CPU, apenas um único thread pode estar em execução a qualquer momento. O sistema
operacional mantém uma fila de threads e aloca tempo de CPU para eles. No entanto, o tempo deve ser compartilhado -
alocar todo o tempo em um único encadeamento seria injusto, pois impediria que outros encadeamentos fizessem seu
trabalho (isso é conhecido como falta de encadeamento). Um sistema operacional multitarefa preventivo é aquele que
suspende um encadeamento (mesmo que ainda tenha trabalho a fazer) para que outros encadeamentos possam ter tempo
de CPU.

Claro, decidir qual thread deve ter tempo de CPU alocado é complicado. O processo de determinar qual thread executar é
chamado de agendamento. Nem todos os sistemas operacionais alocam tempo de thread de maneira justa, mas para dar um
guia ao sistema operacional, os threads são alocados em um nível de prioridade. Alguns threads operam em um nível muito
alto de prioridade, o que significa que eles recebem o primeiro disparo no momento da CPU, enquanto outros threads operam
em um nível muito baixo de prioridade. Threads de baixa prioridade normalmente não obtêm uma parte justa do tempo de
CPU e podem não ser executados se os threads de prioridade mais alta esgotarem todo o tempo de CPU disponível. Uma vez
que a escolha de qual thread é executado depende do sistema operacional e não do aplicativo, torna-se impossível prever a
ordem de execução ou quanto tempo de CPU será fornecido.

Isso tem algumas implicações importantes para os desenvolvedores de aplicativos. Se a ordem de


execução do thread não pode ser prevista, é impossível saber quais tarefas serão concluídas primeiro
ou até mesmo iniciadas. As tarefas podem ser interrompidas antes de serem concluídas, o que pode
causar problemas se outro encadeamento acessar a mesma variável de memória ao mesmo tempo.
Imagine um cenário no qual os registros de dados estão sendo acessados e modificados por vários
threads. Suponha que um encadeamento está gerando um total do número de itens armazenados em
um warehouse e é suspenso parcialmente no total para permitir que outro encadeamento seja
executado. Este segundo thread modifica o conteúdo de um registro para indicar que um item foi
removido. O primeiro thread é retomado e continua em seus cálculos completamente inconsciente de
que os itens foram removidos e que os registros foram modificados.

Esse sistema pode parecer anárquico à primeira vista. Se os threads estão acessando e modificando dados ao acaso,
como então um trabalho útil pode ser executado? Deve-se prestar muita atenção ao acesso simultâneo e à
modificação de dados, para evitar que os dados fiquem fora de sincronia. Com um design cuidadoso, no entanto, os
dados podem ser bloqueados, o que impedirá o acesso de leitura enquanto ocorre o acesso de gravação. A
programação multithread pode ser difícil de dominar, mas as recompensas que ela oferece são grandes. Os clientes
de rede não precisam bloquear a GUI se uma conexão de rede for interrompida e os servidores podem processar
vários clientes simultaneamente. Além disso, os threads podem usar variáveis de forma independente e não são
forçados a compartilhar os mesmos dados. Um encadeamento pode, por exemplo, declarar seu próprio conjunto de
variáveis que não disponibiliza para outros encadeamentos (marcando-os como privados ou protegidos),

7.2 Multi-threading em Java

Java, como muitas linguagens de programação modernas, inclui suporte para aplicativos multithread. Em Java,
threads de execução são representados pelo Java. lang.Thread classe, enquanto o código para tarefas que são
projetadas para serem executadas em um thread separado é representado pelo java.lang.Runnable
interface. É muito importante que os desenvolvedores estejam cientes de ambos.

7.2.1 Criando aplicativos multithread com a classe Thread

133
O java.lang.Thread A classe fornece métodos para iniciar, suspender, retomar e parar um encadeamento, bem como
para controlar outros aspectos, como a prioridade de um encadeamento ou o nome associado a ele. A maneira mais
simples de usar o Fio classe é estendê-lo e substituir o corre() método, que é chamado quando o encadeamento é
iniciado pela primeira vez. Substituindo o corre() método, um thread pode ser feito para executar tarefas úteis em
segundo plano.

NOTA

Lembre-se de que os threads não começam a ser executados automaticamente no momento da


criação. Em vez disso, o Thread.start () método deve ser chamado. Se não estiver, o thread não será
executado.

O exemplo a seguir mostra como estender o Fio classe e iniciar várias instâncias em execução, cada uma em
uma thread separada.

// Capítulo 7, Listagem 1
public class ExtendThreadDemo extends java.lang.Thread {

int threadNumber;

public ExtendThreadDemo (int num) {

// Atribuir à variável de membro


threadNumber = num;
}

// O método Run é executado quando o thread é iniciado pela primeira vez


public void run ()
{

System.out.println ("Eu sou o número do segmento" + threadNumber);

tentar
{
// Dormir por cinco mil milissegundos // (5 segundos), para
simular o trabalho sendo feito Thread.sleep (5000);

}
catch (InterruptedException ie) {}

System.out.println (threadNumber + "está concluído!");


}

// Método principal para criar e iniciar threads


public static void main (String args [])
{
System.out.println ("Criando thread 1");

// Cria a primeira instância de thread Thread t1 = new


ExtendThreadDemo (1);

System.out.println ("Criando thread 2"); // Cria uma segunda


instância de thread
Thread t2 = novo ExtendThreadDemo (2);

134
// Inicie os dois threads t1.start ();
t2.start ();
}
}

Quando compilado e executado, este exemplo mostra os threads sendo criados e a saída de cada um. Os encadeamentos
ficam suspensos por cinco segundos, para simular a ocorrência de trabalho significativo e, em seguida, são encerrados, o que
resulta no fechamento do encadeamento. Somente quando todos os encadeamentos forem encerrados, o método principal
será encerrado. Duas coisas importantes devem ser observadas neste exemplo.

Primeiro, o corre() método não foi invocado quando o thread foi criado, apenas quando o thread foi iniciado
chamando o começar() método. Embora a diferença seja sutil, não deixa de ser importante. Você pode criar threads
com antecedência e iniciá-los somente quando necessário. Lembre-se de que o objeto thread representa apenas um
thread - os threads são, na verdade, fornecidos pelo próprio sistema operacional. Quando o começar() método de um
thread é chamado, ele envia uma solicitação para lançar um thread separado, que irá chamar o corre() método.

NOTA

O aplicativo principal não chama o corre() método diretamente. Em vez disso, ele
chama começar() para realizar esta operação. Se o seu aplicativo chamar corre()
diretamente, ele não será executado como um segmento separado.

Em segundo lugar, o método principal termina quando os dois threads são iniciados. Não há comando de pausa ou
suspensão emitido no thread principal - embora o aplicativo não seja encerrado. Ele continua (embora não tenha nenhum
trabalho real a fazer e esteja ocioso) até que os dois threads tenham terminado seu trabalho e deixem seu corre() método.
Quando um encadeamento normal (também conhecido como encadeamento do usuário) é criado, espera-se que ele conclua
seu trabalho e não seja encerrado prematuramente. A Java Virtual Machine (JVM) não será encerrada até que todos os
encadeamentos do usuário tenham terminado ou até que uma chamada seja feita para o
System.exit () método, que encerra o JVM abruptamente. Às vezes, no entanto, os threads só são úteis quando outros
threads estão em execução (como o aplicativo real, que eventualmente será encerrado quando o usuário terminar de
usá-lo). Chamamos esses tipos de threads de daemon de threads, em oposição a threads de usuário. Se apenas os
encadeamentos daemon estiverem em execução, a JVM será encerrada automaticamente.

A seguir, veremos os encadeamentos daemon em ação, modificando o método principal da seguinte forma (alteração
indicada em negrito) para especificar os dois encadeamentos (t1 e t2) como encadeamentos daemon.

// Método principal para criar e iniciar threads public static void


main (String args []) {

System.out.println ("Criando thread 1");

// Cria a primeira instância de thread Thread t1 = new


ExtendThreadDemo (1);

System.out.println ("Criando thread 2");

// Cria uma segunda instância de thread Thread t2 =


new ExtendThreadDemo (2);

// Torne ambos os threads threads de daemon


t1.setDaemon (true); t2.setDaemon (verdadeiro);

135
// Inicie os dois threads t1.start ();
t2.start ();

tentar

{
// Dormir por um segundo, para permitir que os threads //
tempo para exibir a primeira mensagem
Thread.sleep (1000);
}
catch (InterruptedException ie) {}
}

A primeira mudança torna os encadeamentos daemon t1 e t2, chamando o


Thread.setDaemon (booleano) método. Se você precisar alterar o estado de um thread para um daemon ou thread do
usuário, isso deve ser feito antes do thread ser iniciado - seu estado não pode ser alterado depois que o thread
estiver em execução. A segunda alteração introduz uma pequena pausa, para permitir que os encadeamentos do
daemon exibam sua primeira mensagem. Ao recompilar e executar este exemplo, você notará que os threads não
concluem seu trabalho e exibem sua mensagem final. Isso ocorre porque não há mais threads de usuário ativos
depois que o método principal termina. (O thread principal é sempre um thread do usuário, nunca um thread
daemon.)

7.2.2 Criando aplicativos multithread com a interface executável

Ao estender o Fio classe é uma maneira de criar um aplicativo multithread, nem sempre é a melhor maneira.
Lembre-se de que Java oferece suporte apenas para herança única, ao contrário de linguagens como C ++, que
oferece suporte para herança múltipla. Isso significa que se uma classe estende o
java.lang.Thread classe, ele não pode estender qualquer outra classe - um recurso que muitas vezes é
necessário no design de software Java. A melhor maneira é muitas vezes implementar o java.lang.Runnable
interface.

O Executável interface define um único método, corre() , que deve ser implementado. As classes
implementam essa interface para mostrar que podem ser executadas como um thread de execução
separado. A assinatura precisa para o método de execução é a seguinte:

public void run ()

O Executável interface não define nenhum outro método ou fornece qualquer funcionalidade específica de thread.
Seu único propósito é identificar classes capazes de rodar como threads. Quando um objeto implementando o Executável
interface é passada para o construtor de um encadeamento, e o encadeamento
começar() método é invocado, o corre() método será chamado pelo segmento recém-criado. Quando o corre() o
método termina, a execução do thread é interrompida.

Existem várias vantagens em usar o Executável interface estendendo o Fio aula. A primeira, conforme mencionado
acima, é que um objeto está livre para herdar de uma classe diferente. Segundo, o mesmo Executável objeto pode ser
passado para mais de um thread, portanto, vários threads simultâneos podem estar usando o mesmo código e
agindo nos mesmos dados. Embora esse uso nem sempre seja recomendado, pode fazer sentido em certas
circunstâncias, desde que o devido cuidado seja tomado para evitar conflitos de acesso aos dados. Terceiro,
aplicativos cuidadosamente projetados podem minimizar a sobrecarga, como a criação de um novo Fio
instância requer memória valiosa e tempo de CPU. UMA Executável instância, por outro lado, não incorre na mesma
carga de um encadeamento e ainda pode ser passado para um encadeamento posteriormente para ser reutilizado e
executado novamente, se necessário.

Abaixo está um exemplo de um aplicativo multi-thread que usa o Executável interface em vez de
uma subclasse do Fio aula.

136
// Capítulo 7, Listagem 2
public class RunnableThreadDemo implementa java.lang.Runnable {

// O método Run é executado quando o thread é iniciado pela primeira vez


public void run ()
{
System.out.println ("Eu sou uma instância do
interface java.lang.Runnable ");
}

// Método principal para criar e iniciar threads public static void


main (String args []) {

System.out.println ("Criando objeto executável");

// Criar objeto executável


Runnable run = new RunnableThreadDemo ();
// Cria um encadeamento e passa o objeto executável System.out.println
("Criando o primeiro encadeamento"); Thread t1 = novo Thread
(executar);

// Cria um segundo encadeamento e passa o objeto executável System.out.println


("Criando um segundo encadeamento"); Thread t2 = novo Thread (executar);

// Inicie os dois tópicos


System.out.println ("Iniciando ambos os threads"); t1.start ();
t2.start ();
}
}

Quando o exemplo é compilado e executado, dois threads podem ser vistos imprimindo uma mensagem no
console. O que é muito diferente neste programa, e no anterior, é que apenas um Executável
objeto foi criado, mas dois threads diferentes o executaram. Embora não houvesse dados compartilhados neste
exemplo, em sistemas mais complexos, os encadeamentos devem compartilhar o acesso aos recursos, para evitar
modificação enquanto um recurso está sendo acessado. Isso é obtido sincronizando o acesso aos recursos (discutido
posteriormente neste capítulo).

7.2.3 Controlando Threads

Conforme mostrado nos dois exemplos anteriores, é relativamente fácil iniciar a execução de uma thread. Existem também
outras maneiras de controlar os fios.

7.2.3.1 Interrompendo um Thread

Leitores atentos podem ter notado que sempre que uma chamada para o Fio. dormir (int)
método foi feito em exemplos anteriores, um manipulador de exceção foi usado. Isso ocorre porque o método do
sono coloca um fio para dormir por um longo período de tempo, durante o qual ele geralmente é incapaz de
despertar. No entanto, se um thread deve ser ativado mais cedo, interromper um thread o despertará; isto é
conseguido invocando o interromper() método. Obviamente, isso requer outro encadeamento para manter uma
referência ao encadeamento em espera.

O exemplo a seguir demonstra a interrupção de um encadeamento, usando o


Thread.interrupt () método:

// Capítulo 7, Listagem 3
public class SleepyHead extends Thread {

137
// O método Run é executado quando o thread é iniciado pela primeira vez
public void run ()
{
System.out.println ("Sinto-me com sono. Acorde-me às oito horas
horas");

tentar
{
// Durma por oito horas
Thread.sleep (1000 * 60 * 60 * 8);

System.out.println ("Essa foi uma boa soneca"); catch


}
(InterruptedException ie)
{
System.err.println ("Só mais cinco minutos ...."); }

// Método principal para criar e iniciar threads


public static void main (String args []) lança
java.io.IOException
{
// Cria um thread 'adormecido' // Inicia o
Tópico adormecido = novo SleepyHead ();

thread em espera
sleepy.start ();

// Avisa o usuário e aguarda a entrada


System.out.println ("Pressione enter para interromper a discussão"); // Interrompe o
System.in.read ();

tópico
sleepy.interrupt ();
}
}

O único propósito do tópico neste exemplo é dormir por muito tempo. Uma vez que o fio está adormecido, ele não
pode despertar por si mesmo. O único curso de ação é enviar uma mensagem de interrupção de outro tópico.
Execute o exemplo e você verá que o thread está ocioso. O encadeamento primário (executando o método principal)
espera que o usuário pressione "entrar" e, em seguida, envia uma mensagem de interrupção (que será capturada, a
menos que o encadeamento em espera tenha despertado por conta própria e encerrado). O thread secundário é
ativado, exibe uma mensagem e, em seguida, termina, permitindo que o aplicativo seja fechado.

7.2.3.2 Parando um Thread

Às vezes, é necessário encerrar um thread antes que sua tarefa seja concluída. Por exemplo, se um cliente de rede
estiver enviando mensagens para um servidor de e-mail em um segundo encadeamento e o usuário quiser cancelar a
operação (talvez para excluir uma chama construída às pressas antes de ser enviada), o encadeamento deve ser
interrompido imediatamente. Um tópico pode enviar uma mensagem de parada para outro tópico, invocando o Thread.stop
() método. Isso requer que o encadeamento de controle (emitindo a mensagem de parada) mantenha uma referência
ao encadeamento que deseja encerrar.

O exemplo a seguir demonstra o uso do Pare() método:

// Capítulo 7, Listagem 4
public class StopMe extends Thread

138
{
// O método Run é executado quando o thread é iniciado pela primeira vez
public void run ()
{
contagem interna = 1;

System.out.println ("Eu posso contar. Observe-me ir!");

para (;;)
{
// Imprime a contagem e a incrementa
System.out.print (count ++ + "");

// Durma por meio segundo


tente {Thread.sleep (500); } pega
(InterruptedException ie) {}
}
}

// Método principal para criar e iniciar o void main lê


público estático (String args [] java.io.IOException ) arremessa

{
// Cria e inicia a contagem através do contador ead
de threads = new StopMe (); counter.start ();

// Avisar o usuário e esperar por inpu t


System.out.println ("Pressione qualquer e nter para parar o tópico
contando ");
System.in.read ();

// Interrompe o thread
counter.stop ();
}
}

Uma vez iniciado, o encadeamento neste exemplo exibirá uma contagem crescente, que continuará indefinidamente
sem terminar. Para interromper a discussão, o Thread.stop () método é usado. Às vezes, é absolutamente necessário
interromper um thread, mas esse método deve ser usado com cautela. Ele foi descontinuado na plataforma Java 2,
devido a um problema potencial que pode causar corrupção de dados. Quando um encadeamento é interrompido
dessa maneira, os monitores de acesso que protegem dois encadeamentos de acessar o mesmo recurso
simultaneamente são liberados, mas o próprio recurso pode estar em um estado inconsistente. Embora isso
raramente aconteça, pode ocorrer sem aviso. Por este motivo, é aconselhável que os threads verifiquem
regularmente se devem ou não continuar (por exemplo, verificando o estado de um sinalizador booleano), em vez de
ter outro thread invoque o Pare()
método.

NOTA

Um excelente exemplo disso é mostrado na documentação da API Java para o


java.lang.Thread.stop () , que inclui código de amostra para a criação de um
encadeamento que pesquisa para ver se deve continuar, em vez de permitir
que seja "interrompido".

139
7.2.3.3 Suspendendo / Retomando Threads

Antes do Java 2, era permitido suspender e retomar os encadeamentos, permitindo que um aplicativo pausasse
os encadeamentos sem interrompê-los permanentemente. Isso foi conseguido usando o Thread.suspend ()
e depois o Thread.resume () métodos. No entanto, esses métodos foram reprovados na plataforma Java 2, pois às
vezes podem causar um conflito (uma situação em que um ou mais encadeamentos aguardam o acesso a um
recurso, mas o bloqueio do recurso não é liberado). Isso pode ocorrer se um encadeamento suspenso bloquear um
monitor e não puder liberá-lo enquanto estiver suspenso. Embora os métodos ainda funcionem, é recomendável que
eles não sejam usados.

7.2.3.4 Rendendo Tempo de CPU

Às vezes, um encadeamento pode estar aguardando a ocorrência de um evento ou pode estar entrando em uma
seção de código onde liberar tempo de CPU para outro encadeamento melhorará o desempenho do sistema ou a
experiência do usuário (por exemplo, após realizar um cálculo que deve ser exibido para o usuário e antes de iniciar
outro). Às vezes, é vantajoso, nessas situações, para um encadeamento ceder tempo de CPU para outro
encadeamento em vez de hibernar por um longo período. Por exemplo, enquanto espera que os dados se tornem
disponíveis a partir de um InputStream , um encadeamento pode gerar tempo de CPU em vez de entrar em suspensão.
Nesta situação, o estático produção() método pode ser usado em vez do
dormir() método. Por exemplo, para que o encadeamento atualmente em execução produza tempo de CPU, o seguinte
método pode ser invocado:

Thread.yield ();

Como esta instrução leva apenas uma única linha de código e não requer interação com outro encadeamento,
nenhum exemplo é fornecido. No entanto, é um método útil de se estar ciente, particularmente em sistemas
complexos onde muitos threads estão lutando pelo tempo de CPU. Lembre-se também de que é um método estático
que afeta apenas o encadeamento em execução - um aplicativo não pode render o tempo de um encadeamento
específico.

7.2.3.5 Esperando até que um Thread esteja morto

Às vezes, é necessário esperar até que um encadeamento termine sua tarefa (por exemplo, para recuperar
os resultados da tarefa invocando um método ou lendo uma variável de membro). Para determinar se um
tópico morreu (ou seja, se o corre() método terminou), o Está vivo() método, que retorna um
boleano valor, pode ser invocado. Mas verificar continuamente o valor retornado por esse método
(conhecido como polling) e, em seguida, dormir ou render, é um uso muito ineficiente do tempo da CPU.
Uma maneira muito melhor é usar o Thread.join () método, que espera que um fio morra. Também existe
uma versão sobrecarregada deste método, que toma como parâmetro um longo valor. Esta versão aguarda
a morte do thread ou o número especificado de milissegundos, o que ocorrer primeiro.

O exemplo a seguir demonstra o uso do Thread.join () método.

// Capítulo 7, Listagem 5
public class WaitForDeath extends Thread {

// O método Run é executado quando o thread é iniciado pela primeira vez


public void run ()
{
System.out.println ("Este tópico parece um pouco doente ....");

// Durma por cinco segundos, tente

{
Thread.sleep (5000);
}

140
catch (InterruptedException ie) {}
}

// Método principal para criar e iniciar threads public static void


main (String args []) throws java.lang.InterruptedException

{
// Cria e inicia o encadeamento Thread dying = new
WaitForDeath (); dying.start ();

// Avisar o usuário e aguardar a entrada


System.out.println ("Aguardando encerramento do thread");

// Espere até a morte


morrendo.join ();

System.out.println ("Thread morreu");


}
}

Quando executado, o exemplo mostra que o encadeamento do aplicativo principal inicia um


encadeamento secundário e aguarda sua morte. Quando a rosca secundária termina seu trabalho, e sua corre()
método termina, o thread principal retornará do Junte() método e notificar o usuário.

7.3 Sincronização
Uma consideração importante ao projetar aplicativos multithread é o conflito sobre o acesso aos dados. Se
dois encadeamentos estão lutando pelo mesmo recurso e um mecanismo para resolver conflitos de acesso
não é colocado em prática, a integridade do aplicativo está em jogo. Há dois mecanismos integrados à
linguagem Java para impedir o acesso simultâneo a recursos: sincronização em nível de método e
sincronização em nível de bloco.

7.3.1 Sincronização em nível de método

A sincronização em nível de método evita que dois threads executem métodos em um objeto ao
mesmo tempo. Os métodos que devem ser "thread-safe" são marcados como sincronizados.
Quando um método sincronizado de um objeto é chamado, um thread tira um bloqueio de objeto
ou monitor. Se outro encadeamento tentar executar qualquer método sincronizado, ele
descobrirá que está bloqueado e entrará em um estado de suspensão até que o bloqueio no
monitor de objeto seja liberado. Se vários threads tentarem executar um método em um objeto
bloqueado, uma fila de threads suspensos se formará. Quando o encadeamento que instituiu o
bloqueio retorna do método, apenas um dos encadeamentos enfileirados pode acessar o objeto -
a liberação de um monitor não permite que mais de um objeto tire um novo monitor. Deve-se
notar, no entanto,

A sincronização em nível de método é um mecanismo comum para sincronizar o acesso aos recursos. Ao
projetar aplicativos multithread ou classes que precisam ser thread-safe para uso em ambientes multithread,
os métodos podem ser sincronizados para evitar perda ou corrupção de dados. A menos que os dados
possam ser acessados atomicamente (em uma operação, sem a possibilidade de uma thread ser suspensa
pela JVM e o controle ser fornecido a outra), a sincronização é necessária. Considere o exemplo simples de um
contador (por exemplo, quantas vezes uma ação como um hit em uma página da Web ocorreu) armazenado
em um arquivo. Este contador pode ser incrementado (lendo o valor atual e escrevendo um novo) ou lido por
múltiplos threads. Se um thread tentar incrementar o valor do

141
contador antes que outro thread conclua a modificação do contador, seu valor pode ser definido por um e
sobrescrito pelo outro. Isso significa que o contador leria um valor inválido. Pior ainda, se duas tentativas de
sobrescrever o valor forem feitas, o arquivo pode estar corrompido. Se os métodos para acessar e modificar o
valor do contador fossem sincronizados, no entanto, apenas um thread poderia realizar uma operação de
gravação em um determinado momento.

O sincronizado A palavra-chave é usada para indicar que um método deve ser protegido por um monitor. Todos os
métodos que podem ser afetados pelo acesso simultâneo devem ser marcados como sincronizados. No entanto, essa
palavra-chave deve ser usada com moderação, pois tem uma desvantagem de desempenho. Embora seja adequado
para aplicativos multithread, em um contexto de thread único resulta em perda de tempo de CPU.

public class SomeClass


{
public synchronized void changeData (...) {

.........
}

public synchronized Object getData (...) {

............
}
}

Vejamos um exemplo prático de conflitos de thread e como eles podem ser resolvidos usando métodos
sincronizados. Suponha que temos um contador que pode ser incrementado e exibir um valor. Se o método
que fornece acesso ao contador não é seguro para thread e leva algum tempo para ser concluído, dois ou
mais threads podem acessá-lo ao mesmo tempo e sobrescrever cada incremento. Antes que o valor do
contador possa ser armazenado, um segundo thread pode escrever um novo, que por sua vez é sobrescrito
(ver Figura 7-3 ) Isso fica ainda mais confuso quando uma leitura é feita. Uma vez que uma atualização foi
perdida, uma contagem imprecisa é obtida. Se alterações frequentes forem feitas no contador, ele se tornará
cada vez mais impreciso.

Figura 7-3. Acesso simultâneo e modificação de dados por threads leva a dados
corrupção.

A solução é tornar o contador thread-safe, sincronizando cada método que executa uma operação de leitura ou
gravação. Se um método sincronizado for usado, apenas uma thread pode atualizar o valor a qualquer momento
(veja Figura 7-4 ) O encadeamento que primeiro invoca um método sincronizado bloqueia o monitor do objeto, que é
liberado apenas quando o método é encerrado. Nenhum outro thread pode acessar qualquer método sincronizado
do objeto contador (embora se vários contadores forem usados, este

142
restrição se aplica apenas a instâncias de contadores individuais, e não a Balcão própria classe). Por esse motivo, os
métodos sincronizados devem ser tão breves quanto possível - threads que vão dormir dentro de um método
sincronizado farão com que todas as outras threads que tentam invocar métodos sincronizados naquele objeto
sejam suspensas.

Figura 7-4. Um contador thread-safe é obtido sincronizando métodos de classe.

Agora veremos a sincronização no nível do método em ação. A classe a seguir define um contador com
métodos de modificação e acesso sincronizados.

// Capítulo 7, Listagem 6 public


class Counter
{
private int countValue;

Balcão público ()
{
countValue = 0;
}

contador público (início interno)


{
countValue = start;
}

// Método sincronizado para aumentar o contador public


synchronized void boostCount () {

int count = countValue;

// Simula processamento e modificação de dados lentos


// A remoção da palavra-chave synchronized demonstrará // o quão impreciso o
acesso simultâneo pode ser. O ajuste // deste valor pode ser necessário em
máquinas mais rápidas. tentar

{
Thread.sleep (5);
}
catch (InterruptedException ie) {}

contagem = contagem + 1;

countValue = count;

143
}

// Método sincronizado para retornar o valor do contador public


synchronized int getCount ()
{
return countValue;
}
}

A próxima classe é um aplicativo que usa vários threads com um único contador. Devido ao
sincronizado palavra-chave, a modificação simultânea do valor do contador é impossível, mas compilar e
executar o exemplo sem a presença do sincronizado palavra-chave revela alguns resultados interessantes.
Conforme previsto, a contagem torna-se imprecisa. No entanto, como a operação de gravação é tão rápida,
ela precisa ser desacelerada em máquinas mais rápidas para permitir que outros threads entrem no aumentarContagem
() método. O ajuste do valor do sono pode ser necessário em máquinas muito rápidas.

// Capítulo 7, Listagem 7
public class CountingThread implementa Runnable {

Counter myCounter;
int countAmount;

// Constrói um thread de contagem para usar o contador especificado public


CountingThread (Counter counter, int amount) {

meuContador = contador;
countAmount = amount;
}

public void run ()


{
// Aumenta o contador o número especificado de vezes para (int i = 1; i <=
countAmount; i ++)
{
// Aumenta o contador
myCounter.increaseCount ();
}
}

public static void main (String args []) lança exceção


{
// Cria um novo contador thread-safe Counter c = new
Counter ();

// Nossa instância executável aumentará o contador // dez vezes, para cada


thread que a executa Runnable runner = new CountingThread (c, 10);

System.out.println ("Iniciando a contagem de threads");

Thread t1 = novo Thread (runner); Thread t2 = novo


Thread (runner); Thread t3 = novo Thread (runner);
t1.start (); t2.start (); t3.start ();

// Espere que todos os três threads terminem t1.join ();


t2.join (); t3.join ();

System.out.println ("O valor do contador é" + c.getCount ());

144
}
}

Três threads são lançados, que incrementam o contador 10 vezes cada. Isso significa que, quando o
sincronizado palavra-chave estiver presente, um valor total de 30 será retornado pelo contador
getCount () método. Sem o sincronizado palavra-chave, cada encadeamento sobrescreve o outro e a contagem
resultante é significativamente menor.

7.3.2 Sincronização em nível de bloco

A sincronização em nível de método é um meio eficaz de impedir o acesso simultâneo a recursos. Mas e se o
recurso não foi projetado como thread-safe e é uma classe preexistente que o desenvolvedor não pode
modificar (como uma classe na API Java ou uma biblioteca de terceiros)? A sincronização em nível de bloco,
neste caso, é a melhor opção.

A sincronização em nível de bloco usa o sincronizado palavra-chave, mas em vez de colocar um bloqueio em torno de
métodos específicos, um bloqueio é colocado em torno de blocos de código. Um bloco de código é sincronizado com
um objeto específico e qualquer thread que tente entrar nesse bloco de código é bloqueado, até que o monitor do
objeto especificado seja liberado. O seguinte snippet de código mostra a sintaxe para um bloco sincronizado:

sincronizado (Objeto o)
{
......
}

A sincronização em nível de bloco bloqueia em um objeto específico. Isso significa que vários blocos podem proteger
o acesso ao mesmo objeto, portanto, a sincronização em nível de bloco pode ser aplicada no código de thread
sempre que um objeto é acessado ou modificado. O exemplo a seguir demonstra a sincronização em nível de bloco. É
uma variação do exemplo anterior, mas em vez de criar uma classe separada para representar um contador, uma
variável de instância é usada. O acesso e modificação da variável ocorre dentro do corre() método de um thread,
portanto, a sincronização em nível de método não pode ser usada. Em vez disso, a sincronização em nível de bloco
protege a contagem e garante que, quando a contagem for gravada em um String Buffer ( que é uma string que pode
ser anexada), é escrito na ordem correta.

// Capítulo 7, Listagem 8
public class SynchBlock implementa Runnable {

StringBuffer buffer;
contador interno;

public SynchBlock ()
{
buffer = novo StringBuffer (); contador = 1;

}
public void run ()
{

sincronizado (buffer)
{
System.out.print ("Iniciando bloco sincronizado"); int tempVariable =
counter ++;
int tempVariable = counter ++;

// Criar mensagem para adicionar ao buffer, incluindo alimentação de linha


String message = "Count value is:" + tempVariable +

145
System.getProperty ("line.separator");

tentar
{
Thread.sleep (100);
}
catch (InterruptedException ie) {}

buffer.append (mensagem);
System.out.println ("... finalizando bloco sincronizado");
}
}

public static void main (String args []) lança exceção


{
// Cria uma nova instância executável SynchBlock block = new
SynchBlock ();
Bloco SynchBlock = novo SynchBlock ();

Thread t1 = novo Thread (bloco); Thread t2 =


novo Thread (bloco); Thread t3 = novo Thread
(bloco); Thread t4 = novo Thread (bloco);

t1.start (); t2.start (); t3.start (); t4.start ();

// Espere que todos esses threads terminem t1.join (); t2.join ();
t3.join (); t4.join ();

System.out.println (block.buffer);
}
}

7.4 Comunicação Interthread


Embora os threads possam, é claro, agir isoladamente (na verdade, um design que não requer comunicação
entre os threads se presta a uma implementação muito mais simples), às vezes é necessário que os threads se
comuniquem entre si. Freqüentemente, o tipo de comunicação será bastante simples, como ler ou modificar
uma variável de membro público ou invocar um método de objeto. Outras vezes, é necessária uma
comunicação mais sofisticada. Duas boas opções de comunicação são os canais de comunicação e o esperar() / notificar
() métodos, que permitem que um thread notifique um thread em espera de um evento.

7.4.1 Canais de Comunicação entre Threads

Como a comunicação multiprocesso, que usa canais para enviar dados de um processo para outro, os threads
também podem enviar dados diretamente de um thread para outro. Isso é obtido usando tipos especiais de fluxos
de entrada e saída, que são vinculados. Ao passar uma das extremidades do tubo para outro encadeamento, esse
encadeamento pode ouvir ou falar com outro encadeamento. Na verdade, não há nenhuma restrição que impeça o
uso de dois canais - um segmento pode até ter comunicação bidirecional com outro (consulte Figura 7-5 )

Figura 7-5. A comunicação canalizada é apenas uma via, mas podem ser usados dois canos.

146
A comunicação por meio de tubos não é muito diferente de usar qualquer outra forma de fluxo de entrada ou fluxo
de saída. Outros fluxos também podem ser conectados a um tubo, para facilitar a leitura e a escrita. O exemplo a
seguir demonstra o uso de canais na comunicação de thread.

import java.io. *;

// Capítulo 7, Listagem 8
public class PipeDemo extends Thread {

Saída PipedOutputStream;

// Cria uma instância da classe PipeDemo public PipeDemo


(PipedOutputStream out)
{
// Copiar para a variável de membro local output
= out;
}

public static void main (String args []) {

tentar
{
// Crie um tubo para escrever
PipedOutputStream pout = new PipedOutputStream ();

// Crie um canal para leitura e conecte-o ao // canal de saída

PipedInputStream pin = novo PipedInputStream (beicinho);

// Crie um novo encadeamento de demonstração de tubo, para escrever em


// nosso canal
PipeDemo pipedemo = novo PipeDemo (beicinho);

// Inicie o thread
pipedemo.start ();

// Lê dados de thread, int input =


pin.read ();

// Termina quando o final do stream é alcançado enquanto


(input! = -1)
{
// Imprimir mensagem
System.out.print ((entrada char));

// Lê o próximo byte input =


pin.read ();
}
}
catch (exceção e)

147
{
System.err.println ("Erro de tubulação" + e);
}
}

public void run ()


{
tentar
{
// Cria um printstream para escrita conveniente PrintStream p = new
PrintStream (output);

// Imprimir mensagem
p.println ("Olá de outro encadeamento, via tubos!");

// Fechar o stream
p.close ();
}
catch (exceção e)
{
// nenhum código requerido}

}
}

A execução deste exemplo criará um canal unilateral entre o encadeamento do aplicativo principal e um segundo
encadeamento que envia uma mensagem de texto pelo tubo. O tubo deve ser construído antes do encadeamento
ser iniciado, entretanto, e deve ser passado de alguma forma para o encadeamento, para que tenha uma referência
a um objeto de tubo. Nesse caso, o pipe é passado para o construtor do thread - uma maneira conveniente de
inicializar o thread e colocar o pipe lá.

7.4.2 Notificando um Thread em Espera de um Evento

Um requisito comum na programação multithread é que um thread não pode prosseguir até a
conclusão de uma tarefa por outro thread. Às vezes, um thread estará produzindo informações ou
usando recursos. Outras vezes, a ordem de execução é importante e uma tarefa não pode ser realizada
antes que outra seja concluída. Embora seja possível que um thread espere até que outro morra
(indicando, assim, que o trabalho foi concluído) usando o Thread.join () método, e se um encadeamento
executa uma tarefa contínua e nunca termina?

A solução é notificar outros threads de que uma tarefa foi concluída. Os threads esperam até serem
notificados e a notificação pode ser um processo repetido (com vários ciclos de espera e notificação). Isso
permite que os encadeamentos sincronizem suas ações e comuniquem que um evento crítico ocorreu, sem
exigir a complexidade extra de comunicação baseada em tubos ou métodos de chamada. Às vezes, um
thread pode nem saber exatamente quais threads estão aguardando sua conclusão, portanto, um tipo
especial de notificação é usado.

Cada objeto Java herda do java.lang.Object classe (a superclasse de cada objeto) a capacidade de manter uma fila de
threads aguardando a liberação de um bloqueio de objeto e de notificar uma ou mais threads em espera de que o
objeto foi liberado. Isso fornece uma ótima maneira de notificar um encadeamento de que ocorreu um evento e
de os encadeamentos aguardarem indefinidamente (ou por um período limitado de tempo) até que a notificação
seja enviada.

Para que os threads esperem por um período indefinido de tempo, o Object.wait () método é usado. Também existe uma
versão sobrecarregada desse método, que espera por um período de tempo limitado (especificado em milissegundos). Antes
de o esperar() método pode ser chamado, no entanto, o encadeamento deve conter um bloqueio no monitor do objeto. Para
obter um bloqueio no monitor de um objeto, ele deve estar executando um

148
método sincronizado ou usando um bloco sincronizado. Quando o bloqueio é liberado, outro encadeamento pode
obtê-lo - sem isso, o encadeamento aguardará indefinidamente.

Uma vez o esperar() método é executado, o monitor é liberado e o encadeamento é suspenso até que um
a chamada é feita para o Object.notify () ou Objeto. notificar todos () método. Para despertar
threads em espera, outra thread pode chamar qualquer um dos métodos. No entanto, o notificar () método irá notificar apenas
um único thread, mesmo se vários threads estiverem esperando. Também não há escolha sobre qual encadeamento será
ativado (isso é determinado pela implementação da JVM, portanto, você não pode contar com, por exemplo, uma fila FIFO),
portanto, é aconselhável que o notificar tudo () método é usado se você deseja notificar um segmento específico.

O exemplo a seguir demonstra o uso dos métodos de espera / notificação.

// Capítulo 7, Listagem 9
public class WaitNotify extends Thread {

public static void main (String args []) lança Exception {

Thread notificationThread = new WaitNotify ();


notificationThread.start ();

// Aguarde o thread de notificação acionar o evento sincronizado


(notificationThread)
{
notificationThread.wait ();
}

// Notifica o usuário que o método wait () retornou System.out.println ("A


espera acabou");
}

public void run ()


{
System.out.println ("Pressione Enter para interromper a espera do thread");

tentar
{
System.in.read ();
}
catch (java.io.IOException ioe)
{
// nenhum código requerido}

// Notifica quaisquer threads aguardando neste thread


sincronizado (this)
{
this.notifyAll ();
}
}
}

O exemplo é bastante simples. O encadeamento do aplicativo principal espera até ser notificado pelo segundo
encadeamento de que ocorreu um evento. Até que o usuário pressione a tecla "enter", o segundo tópico não enviará
a mensagem de notificação. Embora o aplicativo seja encerrado assim que a notificação for enviada e recebida, uma
série de esperar() / notificar ( ) poderiam ocorrer ciclos, para indicar eventos irregulares, mas de ocorrência frequente.

149
7.5 Grupos de Tópicos

Embora os encadeamentos possam ser criados e executados individualmente, às vezes é mais fácil trabalhar com
encadeamentos como um grupo, em vez de um por vez. Operações que afetam threads, como suspender e retomar
threads, pará-los a frio ou interrompê-los, podem ser realizadas em threads individuais, mas isso requer que os
desenvolvedores mantenham uma lista de threads (usando uma estrutura de dados como um vetor ou um array) .
Quando uma operação deve ser realizada, cada um dos encadeamentos nesta lista deve ser percorrido e, em
seguida, executado. Isso cria trabalho extra para os desenvolvedores, exigindo um código mais complexo. Uma
alternativa mais fácil é agrupar threads e aplicar uma operação no grupo, em vez de nos elementos individuais do
grupo.

A API Java fornece suporte para grupos de threads na forma de ThreadGroup aula. O propósito do ThreadGroup classe
deve representar uma coleção de threads e fornecer métodos que atuam como atalhos para invocar métodos
em threads individuais na coleção. Ele também fornece uma maneira de reunir informações sobre threads
comumente relacionados, como o número de threads em um grupo e referências aos threads armazenados
em um grupo. Se o acesso a threads individuais for necessário, ele pode ser obtido no ThreadGroup , em vez de
ter que criar alguma outra estrutura de dados para atuar como um contêiner.

Quando uma JVM é iniciada pela primeira vez e um aplicativo Java é executado, o encadeamento do aplicativo principal irá
executar a Principal() método. Depois disso, o aplicativo fica livre para criar grupos de threads, ou threads individuais não
associados a um grupo específico. Um grupo pode ser composto de quantas threads forem necessárias, e pode ser
adicionado dinamicamente a qualquer ponto durante a execução de um programa (veja Figura 7-
6 ) No entanto, não há como retirar um thread (ou um grupo de threads) de um grupo. Observe, também, que em ambientes
controlados, como gerenciadores de segurança customizados, applets em execução em um navegador ou código em
execução em um mecanismo de servlet, podem ser colocadas restrições na criação de novos grupos de encadeamentos ou
no acesso aos existentes.

Figura 7-6. Os grupos de threads podem adicionar threads adicionais a qualquer momento.

150
Além disso, um grupo de tópicos pode conter grupos de tópicos adicionais, que são contados como subgrupos
( Figura 7-7 ) Operações como parar ou suspender podem ser realizadas nos subgrupos ou no grupo pai.
Quando uma operação é aplicada ao grupo pai, ela fará com que a operação seja propagada para cada
subgrupo, que por sua vez, passará a operação para cada thread dentro daquele subgrupo. Isso significa que
uma única operação pode ser chamada em várias, dezenas ou até centenas de threads. Isso torna o design
muito mais limpo - não há necessidade de uma quantidade infinita de loops iterando em uma lista de threads.
A inclusão de subgrupos permite um controle mais refinado sobre as operações de rosca, pois os grupos
selecionados podem ser acionados enquanto outros são deixados intactos.

Figura 7-7. Os grupos de tópicos podem conter tópicos e subgrupos, que por sua vez podem
incluir mais grupos e tópicos.

7.5.1 Criando um Grupo de Threads

Construtores

Existem dois construtores para o ThreadGroup aula:

• public ThreadGroup (nome da string) arremessa java.lang.Security


Exceção - cria um novo grupo de discussão, que pode ser identificado pelo especificado
Fragmento nome. O grupo pai do ThreadGroup será o pai do segmento em execução no
momento.
• public ThreadGroup (ThreadGroup parentGroup, String name) arremessa
java.lang.SecurityException - cria um novo grupo de threads (que pode ser identificado
pelo nome da String especificada), cujo grupo pai será o especificado
ThreadGroup . Este construtor permite que um grupo seja armazenado como um subgrupo, em vez de ser
criado como um grupo separado.

Por exemplo, para criar um grupo e subgrupo, o seguinte código pode ser usado:

151
ThreadGroup pai = novo ThreadGroup ("pai");
Subgrupo ThreadGroup = novo ThreadGroup (pai, "subgrupo");

7.5.2 Usando um Grupo de Threads

Depois de criado, um grupo de encadeamentos pode ser usado da mesma maneira que um encadeamento normal - pode ser
suspenso, retomado, interrompido e interrompido chamando o método apropriado. No entanto, uma etapa adicional deve
ser realizada antes que o encadeamento possa ser usado com eficácia - ele deve conter encadeamentos. Afinal, um grupo de
encadeamentos vazio não tem muita utilidade.

Construtores

Threads são associados a um determinado grupo no momento de sua criação. Portanto, não é possível
atribuir um tópico a um grupo posteriormente, ou mover um tópico de um grupo para outro. Existem
três construtores para o Java. lang. Fio classe que aceita grupos de threads como parâmetro:

• Thread (grupo ThreadGroup, Runnable runnable) Thread (grupo


• ThreadGroup, nome da String)
• Thread (grupo ThreadGroup, Runnable runnable, String name)

NOTA

Quando um encadeamento é criado e nenhum grupo é atribuído ao construtor, o grupo de


encadeamentos do encadeamento atualmente em execução será atribuído. Especificar um
grupo dá aos desenvolvedores a escolha de em qual grupo um thread será localizado.

Uma vez que os threads são associados ao grupo, as operações do grupo podem ser realizadas, o número de
threads / grupos que estão ativos pode ser verificado e uma lista de threads pode ser obtida, simplesmente
invocando o apropriado ThreadGroup método.

Métodos

Os seguintes métodos do ThreadGroup classe são públicas, a menos que indicado de outra forma.

• int activeCount () - retorna o número de tópicos ativos neste grupo e quaisquer


subgrupos deste grupo.
• int activeGroupCount () - retorna o número de grupos que possuem tópicos ativos neste
grupo e quaisquer subgrupos.
• boolean allowThreadSuspension () - indica se um tópico pode ser
suspenso sob certas condições. O comportamento desse método não está bem definido
e foi reprovado na plataforma Java 2.
• void checkAccess () arremessa java.lang.SecurityException - joga um
exceção de segurança se o gerenciador de segurança atualmente instalado proibir o encadeamento em
execução de modificar o grupo de encadeamentos.
• void destroy () arremessa java.lang.IllegalThreadStateException,
java.lang.SecurityException - destrói este grupo de encadeamentos e cada um de seus subgrupos,
desde que eles não contenham nenhum encadeamento ativo (ou seja, encadeamentos que ainda
não foram interrompidos). Se este não for o caso, um IllegalThreadStateException será lançado.
Antes de este método ser invocado, é aconselhável que o ThreadGroup.stop ()
método é chamado.

152
• enumerar int (Thread [] threadList) arremessa java.lang.Security -
Exceção - coloca na matriz especificada uma referência a cada um dos encadeamentos ativos neste grupo e
a qualquer um de seus subgrupos. Se, no entanto, o tamanho da matriz não for grande o suficiente para
acomodar o número de threads ativos, não
ArrayOutOfBoundsException será lançado - o método, em vez disso, deixará de fora os threads
extras e o aplicativo pode permanecer alheio a isso. Por esse motivo, um array suficientemente
grande deve ser usado. O número de threads copiados na matriz é retornado por este método,
não o número de threads que deve foram copiados. Para determinar o número de threads
ativos (com o propósito de criar uma matriz), uma chamada deve ser feita para o ThreadGroup.
contagem ativa () método. Tópicos adicionais podem, no entanto, ser adicionados ao grupo,
então esta chamada deve ser feita antes de invocar o enumerar método.

• enumerar int (Thread [] threadList, boolean subgroupFlag) arremessa


java.lang.SecurityException - coloca no array especificado uma referência a todos os threads ativos
neste grupo. Se o subgrupo boleano flag é definido como "true", threads de subgrupos também
serão incluídos. Uma matriz suficientemente grande deve ser usada (para determinar o
comprimento desta matriz, consulte o enumerar (Thread [] threadList) descrição do método). No
entanto, como o activeCount () método retorna o número de threads ativos neste grupo, e qualquer
subgrupo, o tamanho da matriz pode ser muito grande. Por esse motivo, os aplicativos devem
verificar o valor de retorno desse método, que indica o número de threads armazenados no array.

• enumerar int (ThreadGroup [] groupList) arremessa java.lang.


Exceção de segurança - coloca uma referência a todos os grupos ativos e quaisquer subgrupos na
matriz especificada. Conforme avisado no enumerar (Thread [] threadList) descrição do método, a
matriz deve ser alocada para um tamanho suficientemente grande para acomodar o número
de grupos de encadeamentos ativos. Isso pode ser alcançado chamando o ThreadGroup.
GroupCount ativo () método e alocar esse número de elementos na matriz. O número de grupos
copiados para a matriz será retornado por este método.
• enumerar int (ThreadGroup [] groupList, boolean subgroupFlag)
arremessa java.lang.SecurityException - armazena uma referência a todos os grupos ativos na matriz
especificada. Se o sinalizador booleano do subgrupo for definido como "verdadeiro", os subgrupos
também serão incluídos e, se definido como "falso", apenas os grupos do grupo atual serão incluídos (ou
seja, um nível de profundidade). Como com todos os outros ThreadGroup.enumerate (...) métodos, um
array suficientemente grande deve ser criado, ou grupos de threads extras serão excluídos. O valor de
retorno desse método indica o número de grupos armazenados na matriz.
• int getMaxPriority () - retorna o nível de prioridade máxima (discutido anteriormente nesta seção) para
threads neste grupo. Threads não podem exceder o nível de prioridade de seu grupo, embora um
erro não seja gerado se for feita uma tentativa de fazê-lo.
• String getName () - retorna o nome associado a este grupo de discussão.
• ThreadGroup getParent () arremessa java.lang.SecurityException - retorna
o grupo pai deste grupo de discussão. Todos os threads fazem parte de algum grupo, embora o
acesso ao grupo pai possa causar o lançamento de uma exceção.
• void interrupt () arremessa java.lang.SecurityException - invoca o
Thread.interrupt () método de cada thread neste grupo e quaisquer subgrupos.
• boolean isDaemon () - retorna "verdadeiro" se o grupo de threads for um grupo daemon e "falso" se não
for. Um grupo de daemon pode ser destruído quando todos os threads ativos são encerrados.
• boolean isDestroyed () - retorna "verdadeiro" se o grupo de encadeamentos foi destruído.
• void list () - despeja informações sobre o grupo de discussão para System.out . Este método
é útil durante o teste, para mostrar os threads e grupos que são membros.
• boolean parentOf (ThreadGroup otherGroup) - testa se o especificado
grupo de encadeamentos é um grupo filho.

• void resume () - método obsoleto que retoma todas as threads suspensas no grupo e
quaisquer subgrupos. O motivo da suspensão é porque o Thread.resume ()
método foi marcado como depreciado na plataforma Java 2.

153
• void setDaemon (sinalizador booleano) arremessa java.lang.Security
Exceção —Modifica o sinalizador do daemon do grupo de encadeamentos. Um valor "true" torna o grupo de

encadeamentos um grupo de daemons, o que significa que ele pode ser destruído quando todos os
encadeamentos ativos são encerrados.
• void setMaxPriority (prioridade interna) arremessa java.lang.Security
Exceção - atribui um novo nível máximo de prioridade para este grupo. Este método não redefine a
prioridade dos threads existentes (ou seja, um thread de prioridade mais alta que faz parte do grupo
não terá sua prioridade reduzida, mas pode não aumentar sua prioridade ainda mais).
• void stop () arremessa java.lang.SecurityException - método obsoleto que
invoca o Thread.stop () método para todos os tópicos neste grupo e quaisquer subgrupos. Enquanto
o Thread.stop () método está obsoleto na plataforma Java 2, este método também está obsoleto.

• void suspend () arremessa java.lang.SecurityException - método obsoleto


que invoca o Thread.suspend () método para todos os tópicos neste grupo e quaisquer
subgrupos. Enquanto o Thread.suspend () método está obsoleto na plataforma Java 2, este
método também está obsoleto.
• String toString () - retorna uma versão String do grupo de encadeamentos.
• void uncaughtException (thread de discussão, erro de lançamento) - chamado
sempre que um thread falha em capturar uma exceção em tempo de execução. Os aplicativos que
desejam detectar esses tipos de erros (por exemplo, para fins de registro) devem estender o ThreadGroup
classe e sobrescrever este método.

7.5.2.1 Demonstração da classe ThreadGroup

O exemplo a seguir mostra a construção de grupos de encadeamentos e o uso do Lista()


método para exibir o estado dos threads ativos.

// Capítulo 7, Listagem 11
public class GroupDemo implementa Runnable {

public static void main (String args []) lança Exception {

// Criar um grupo de discussão


ThreadGroup pai = novo ThreadGroup ("pai");

// Cria um grupo que é filho de outro grupo de threads ThreadGroup subgroup = new
ThreadGroup (parent, "subgroup");

// Cria alguns threads no pai e na classe de subgrupo Thread t1 = new Thread (pai,
new GroupDemo ()); t1.start ();

Thread t2 = novo Thread (pai, novo GroupDemo ()); t2.start ();

Tópico t3 = novo Tópico (subgrupo, novo GroupDemo ()); t3.start ();

// Despeja o conteúdo do grupo em System.out parent.list ();

// Espere pelo usuário e, em seguida, encerre


System.out.println ("Pressione enter para continuar"); System.in.read
();
System.exit (0);
}

public void run ()


{

154
// Fazer nada
para(;;)
{
Thread.yield ();
}
}
}

7.6 Prioridades de discussão

Conforme mencionado no início do capítulo, o multi-threading é obtido alternando rapidamente entre um


thread e outro para simular a execução simultânea de código. A menos que haja várias CPUs, o sistema
operacional alternará entre um thread e outro com base em um algoritmo arbitrário sobre o qual threads
individuais não têm controle. Portanto, é impossível prever a ordem em que as threads serão executadas ou
quando (se houver) uma thread será agendada. O melhor que pode ser feito é sugerir a prioridade relativa
dos encadeamentos, o que dá ao sistema operacional uma indicação de quais encadeamentos são mais
importantes (e devem ser agendados com mais frequência ou por um período mais longo).

7.6.1 Atribuindo uma Prioridade de Tópico

Em Java, uma classificação numérica especifica a prioridade do encadeamento, com 10 sendo o tipo de prioridade mais alta e 1 sendo o
mais baixo. Algumas prioridades de thread são definidas como variáveis de membro estático do
java.lang.Thread classe, um atalho conveniente que elimina a necessidade de lembrar um valor
numérico. Tabela 7.1 mostra as prioridades numéricas do thread e atalhos associados.

Tabela 7-1. Prioridades de discussão

10 Thread.MAX_PRIORITY
7
8
7
6
5 Thread.NORM_PRIORITY
4
3
2
1 Thread.MIN_PRIORITY

Para definir a prioridade do thread, o Thread.setPriorityMethod (int) método é usado. Por exemplo,
para definir um thread com o nível de prioridade mínimo, o seguinte código seria usado:

Thread t = novo Thread (executável);


t.setPriority (Thread.MIN_PRIORITY); t.start ();

7.6.2 Obtendo a Prioridade de Thread Atual

Um encadeamento que deseja determinar sua prioridade de encadeamento atual (por exemplo, para ver se
é alta o suficiente e tomar uma ação corretiva se não for), pode fazê-lo invocando o
Thread.getPriority () método. Este método retorna um valor int, que indica o

155
prioridade do segmento. Por exemplo, para obter a prioridade do thread em execução no momento, o
seguinte código pode ser usado:

Tópico t = Thread.currentThread ();


System.out.println ("Prioridade:" + t.getPriority ());

7.6.3 Limitando a prioridade do thread

Às vezes, pode ser necessário limitar a prioridade máxima que um thread pode solicitar (por exemplo, se
estiver executando um código não confiável baixado da rede). É possível instalar um gerenciador de
segurança personalizado, o que geraria um Exceção de segurança , mas isso envolve uma quantidade significativa
de esforço. Uma alternativa muito mais fácil é criar um ThreadGroup ( abordado na seção anterior), e atribua um
nível máximo de prioridade a este grupo. Ao criar um encadeamento, o grupo de encadeamentos é
especificado. Qualquer thread que solicitar uma prioridade mais alta não a receberá, mas também não será
gerada uma mensagem de erro. Isso cria uma solução limpa para o problema.

Para atribuir uma prioridade máxima de thread para um grupo, o ThreadGroup. definir prioridade máxima (int) método
é usado. Por exemplo, para definir uma prioridade de thread de 8, o seguinte código seria usado:

Grupo ThreadGroup = novo ThreadGroup ("meugrupo");


group.setMaximumPriority (8);

7,7 Resumo
Uma compreensão da programação multithread é importante para qualquer tipo de programação de aplicativo ou
applet, mas particularmente para desenvolvimento de software para ambientes de rede. As redes são lentas e não
confiáveis, portanto, o código de rede frequentemente precisará ser executado em uma thread separada daquela da
interface do usuário, para evitar que um sistema trave ou paralise devido a mudanças no estado da rede. Além disso,
a maioria dos softwares de rede interage com vários clientes e / ou servidores e, a menos que as operações sejam
extremamente rápidas (como receber e despachar um pacote UDP), vários threads serão necessários para que as
interações possam ocorrer simultaneamente.

Destaques do capítulo

Neste capítulo, você aprendeu:

• Como criar aplicativos multithread, estendendo o Como


java.lang.Thread aula
• criar aplicativos multithread implementando o
java.lang.Runnable interface
• Como interromper, parar e esperar a morte de tópicos
• Como se proteger contra acesso simultâneo a recursos, usando sincronização em nível
de método e nível de bloco
• Como se comunicar entre threads usando pipes. Como notificar
• threads em espera de um evento. Como agrupar threads, usando o ThreadGroup
• classe Como acessar e modificar as prioridades de agendamento

156
A execução simultânea de software, no entanto, apresenta complexidades. Sincronização de acesso a dados,
comunicação entre threads e agendamento de threads são componentes difíceis para o programador dominar e,
portanto, até mesmo desenvolvedores experientes encontram problemas quando confiam demais na previsão da
ordem de execução da thread. Em muitas situações, é importante que a sincronização de acesso aos recursos seja
usada para preservar a integridade dos dados e a ordem das operações. Além disso, a comunicação entre os
encadeamentos pode ser empregada para auxiliar os encadeamentos na colaboração para concluir tarefas.

157
Capítulo 8. Implementando Protocolos de Aplicativos

Este capítulo apresenta uma visão geral dos protocolos de aplicativos comuns da Internet e fornece
exemplos de código de três implementações de protocolo em Java. Aqui, usamos as técnicas abordadas em
Capítulo 6 para criar software cliente e servidor usando sockets que suportam protocolos populares da Internet.

8.1 Visão Geral

Um protocolo de aplicativo facilita a comunicação entre aplicativos, usando os serviços de camadas de modelo
de Open Systems Interconnection (OSI) de nível inferior (por exemplo, camadas de rede e físicas). Quando
você verifica seu e-mail, navega em um site da Web, joga ou baixa arquivos da Internet, o software que você
executa está usando um protocolo de aplicativo para comunicação.

Para que os aplicativos interoperem, a implementação de protocolos de aplicativos deve ser precisa. Você não pode
ter um aplicativo falando de uma maneira e outro aplicativo incapaz de entender ou interpretar a mensagem - caso
contrário, os muitos milhares de aplicativos de software em execução nos muitos milhões de computadores
existentes não poderiam se dar bem. Isso não quer dizer que toda implementação oferecerá suporte a todos os
recursos de um protocolo, ou que eles serão implementados da mesma maneira, na mesma linguagem ou na mesma
máquina. No entanto, as implementações de protocolo devem, pelo menos externamente, se comportar da mesma
maneira e, quando não podem atender a uma solicitação ou oferecer suporte a um recurso, devem comunicar isso
de forma eficaz, usando um processo comumente compreendido.

A semântica de um protocolo é apresentada em um documento de especificação de protocolo. Isso permite que os


desenvolvedores implementem o mesmo protocolo em uma variedade de plataformas e linguagens e façam com que eles se
comuniquem com sucesso.

8.2 Especificações de protocolo de aplicativo

No início da história da Internet, um sistema foi projetado para facilitar a distribuição de especificações de
protocolo novas e atualizadas para implementadores de protocolo. O projeto de um protocolo de rede tende
a ser um processo evolutivo envolvendo muitos colaboradores, com rascunhos iniciais disponibilizados ao
público e reescritas realizadas. Quando o protocolo está quase concluído e pronto para ser implementado
pelos aplicativos, ele é publicado como um documento de solicitação de comentário (RFC).

A maioria dos protocolos populares da Internet em uso hoje são publicados como RFCs. Cada RFC detalha um único
protocolo ou ideia sobre a Internet e recebe um número de identificação (por exemplo, RFC 1945 para HTTP / 1.0).
Esses documentos são extremamente detalhados e contêm todas as informações necessárias para a implementação
do protocolo. No entanto, nem todo RFC cobre um protocolo - alguns são visões gerais, notas sobre a arquitetura da
Internet ou sugestões para revisões de protocolos existentes.

NOTA

Há até mesmo uma tentativa ocasional de humor em documentos RFC - para aqueles
com interesse em todas as coisas Java, tanto a linguagem quanto a bebida, você
pode querer dar uma olhada no RFC 2324, o Hyper Text Coffee Pot Control Protocol.

158

Você também pode gostar