3.1 Objeto ativo
Um objeto Active é um padrão de design que separa o thread de execução de um método do thread no qual ele foi chamado. A finalidade desse padrão é fornecer execução paralela usando chamadas de método assíncronas e um agendador de processamento de solicitação.
Versão simplificada:
Variante clássica:
Este modelo tem seis elementos:
- Um objeto proxy que fornece uma interface para os métodos públicos do cliente.
- Uma interface que define métodos de acesso para o objeto ativo.
- Lista de solicitações recebidas de clientes.
- Um agendador que determina a ordem na qual as consultas devem ser executadas.
- Implementação de métodos de objetos ativos.
- Um procedimento de retorno de chamada ou uma variável para o cliente receber um resultado.
3.2 bloqueio
O padrão Lock é um mecanismo de sincronização que permite acesso exclusivo a um recurso compartilhado entre vários threads. Os bloqueios são uma maneira de impor a política de controle de simultaneidade.
Basicamente, um soft lock é usado, com a suposição de que cada thread tente “adquirir o bloqueio” antes de acessar o recurso compartilhado correspondente.
No entanto, alguns sistemas fornecem um mecanismo de bloqueio obrigatório pelo qual uma tentativa de acesso não autorizado a um recurso bloqueado será abortada lançando uma exceção no thread que tentou obter acesso.
Um semáforo é o tipo mais simples de bloqueio. Ao nível do acesso aos dados não é feita qualquer distinção entre os modos de acesso: partilhado (leitura-só) ou exclusivo (leitura-escrita). No modo compartilhado, vários threads podem solicitar um bloqueio para acessar dados no modo somente leitura. O modo de acesso exclusivo também é usado nos algoritmos de atualização e exclusão.
Os tipos de bloqueios se diferenciam pela estratégia de bloquear a continuação da execução da thread. Na maioria das implementações, uma solicitação de bloqueio impede que o encadeamento continue a executar até que o recurso bloqueado esteja disponível.
Um spinlock é um bloqueio que espera em um loop até que o acesso seja concedido. Tal bloqueio é muito eficiente se um thread espera por um bloqueio por um pequeno período de tempo, evitando assim o reescalonamento excessivo de threads. O custo de espera pelo acesso será significativo se uma das threads mantiver o bloqueio por muito tempo.
Para implementar efetivamente o mecanismo de bloqueio, é necessário suporte no nível do hardware. O suporte de hardware pode ser implementado como uma ou mais operações atômicas, como "testar e definir", "buscar e adicionar" ou "comparar e trocar". Essas instruções permitem que você verifique sem interrupção se o bloqueio está livre e, em caso afirmativo, adquira o bloqueio.
3.3 Monitorar
O padrão Monitor é um mecanismo de interação e sincronização de processo de alto nível que fornece acesso a recursos compartilhados. Uma abordagem para sincronizar duas ou mais tarefas de computador usando um recurso comum, geralmente hardware ou um conjunto de variáveis.
Na multitarefa baseada em monitor, o compilador ou interpretador insere de forma transparente o código de bloqueio-desbloqueio em rotinas formatadas apropriadamente, de forma transparente para o programador, evitando que o programador chame explicitamente as primitivas de sincronização.
O monitor é composto por:
- um conjunto de procedimentos que interagem com um recurso compartilhado
- mutex
- variáveis associadas a este recurso
- uma invariante que define condições para evitar uma condição de corrida
O procedimento monitor adquire o mutex antes de iniciar o trabalho e o mantém até que o procedimento termine ou até que uma determinada condição seja esperada. Se cada procedimento garantir que a invariante seja verdadeira antes de liberar o mutex, nenhuma tarefa poderá adquirir o recurso em uma condição de corrida.
É assim que o operador sincronizado funciona em Java com os métodos wait()
e notify()
.
3.4 Bloqueio de verificação dupla
O bloqueio verificado duas vezes é um padrão de design paralelo destinado a reduzir a sobrecarga de obtenção de um bloqueio.
Primeiro, a condição de bloqueio é verificada sem nenhuma sincronização. Um thread tenta adquirir um bloqueio somente se o resultado da verificação indicar que ele precisa adquirir o bloqueio.
//Double-Checked Locking
public final class Singleton {
private static Singleton instance; //Don't forget volatile modifier
public static Singleton getInstance() {
if (instance == null) { //Read
synchronized (Singleton.class) { //
if (instance == null) { //Read Write
instance = new Singleton(); //
}
}
}
}
Como criar um objeto singleton em um ambiente thread-safe?
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
}
Se você criar um objeto Singleton de diferentes threads, pode haver uma situação em que vários objetos são criados ao mesmo tempo, e isso é inaceitável. Portanto, é razoável agrupar a criação do objeto em uma instrução sincronizada.
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
Essa abordagem funcionará, mas tem uma pequena desvantagem. Após a criação do objeto, toda vez que você tentar obtê-lo no futuro, será realizada uma verificação no bloco sincronizado, o que significa que o thread atual e tudo relacionado a ele serão bloqueados. Portanto, este código pode ser otimizado um pouco:
public static Singleton getInstance() {
if (instance != null)
return instance;
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
Em algumas linguagens e/ou em algumas máquinas não é possível implementar esse padrão com segurança. Portanto, às vezes é chamado de antipadrão. Tais recursos levaram ao aparecimento do relacionamento de ordem estrita "acontece antes" no Modelo de Memória Java e no Modelo de Memória C++.
É normalmente usado para reduzir a sobrecarga de implementação de inicialização preguiçosa em programas multiencadeados, como o padrão de design Singleton. Na inicialização preguiçosa de uma variável, a inicialização é adiada até que o valor da variável seja necessário no cálculo.
3.5 Agendador
O Agendador é um padrão de design paralelo que fornece um mecanismo para implementar uma política de agendamento, mas é independente de qualquer política específica. Controla a ordem na qual os threads devem executar o código sequencial, usando um objeto que especifica explicitamente a sequência de threads em espera.
GO TO FULL VERSION