Última Atualização: 08 de Dezembro de 2001, 22:27 BRDT

Utilizando uma referência global

Uma referência global (br.shmem.SharedReference) é um proxy para um objeto distribuído. Através dela, é possível compartilhar um objeto em uma aplicação distribuída utilizando mecanismos simples de compartilhamento e sincronização.

Operações em uma referência global

Compartilhando um objeto

Objetos são compartilhados através de um nome global. Esta implementação permite qualquer java.lang.String como nome, desde que ele seja único no Espaço Global, um espaço virtual de onde é possível acessar todos os objetos distribuídos naquele espaço.

A primeira tarefa que o programa deve executar antes de poder compartilhar objetos é iniciar uma instância da classe SharedMemory (br.shmem.SharedMemory). Para isso, é necessário fornecer o hostname (por exemplo, "sushi.ime.usp.br" e "143.107.45.46" são ambos válidos) do cliente e uma porta para receber conexões. Esses são os dois parâmetros do construtor do objeto.

A seguir, a instância da SharedMemory deve se conectar a um servidor, através do método connect(java.lang.String, int). Os parâmetros são também um hostname e a porta, indicando o endereço e a porta onde o servidor recebe conexões.

Uma vez conectada, a SharedMemory provê as funcionalidades acima para as referências criadas através do método share(java.lang.String, java.io.Serializable, java.lang.String). O primeiro parâmetro é um nome global, como explicado acima. O segundo é um serializable qualquer, e o terceiro é o nome de uma subclasse de br.shmem.SharedReference.

Um objeto compartilhado passa a ser a instância original, a partir da qual réplicas serão criadas para uso na aplicação distribuída. A referência ao Serializable passado como parâmetro não deve mais ser utilizada. Cada thread que for utilizar o objeto deve requisitar uma SharedReference ao SharedMemory através do método get(String), fornecendo o nome global do objeto.

Quando um objeto é removido do espaço global, as cópias nos caches em cada host continuam valendo até que uma operação para removê-las seja chamada.

Leases - mantendo a consistência

A consistência do sistema é mantida pelo uso de leases: ao receber uma cópia do objeto distribuído, cada host recebe um timestamp criado pelo servidor. Esse timestamp corresponde à última modificação que o objeto original recebeu.

Cada cliente recebe, junto com a cópia, um lease. Um lease é um inteiro longo contendo um prazo de validade em milisegundos. Cada vez que o lease expira, a instância da SharedMemory verifica se o objeto foi modificado, usando o timestamp que ela recebeu e comunicando-se com o servidor. Após a validação do cache, aquela cópia fica valendo por mais um período.

Os leases recebem um valor default pelo dono do objeto no momento que ele é compartilhado, mas podem ser modificados pelos outros clientes para garantir uma melhor consistência e aproveitar padrões de uso do objeto.

Transações

Transações sempre começam com uma chamada ao método beginTrans() e terminam com uma chamada a commit() se tudo correu bem, ou uma chamada a rollback() se as alterações precisam ser desfeitas.

Durante uma transação, outras threads podem continuar a ler o objeto, utilizando a última cópia válida. Uma vez terminado o processo com commit(), os outros clientes poderão ver as modificações.

Atenção! Todas as escritas em um objeto devem fazer parte de uma transação. Caso contrário, o efeito é imprevisível. Por isso mesmo, é importante construir classes e proxies bem definidas e coerentes, como veremos a seguir.

Construindo e utilizando proxies

O único pré-requisito formal para compartilhar um objeto é que ele implemente a interface java.io.Serializable. Quando o cliente requisita o objeto através de seu nome global, uma instância de SharedReference é usada como proxy para acessar os métodos daquele objeto.

Dado a complexidade da implementação de um download automático de código, e a necessidade de não expor a instância do objeto replicado no cliente à aplicação que o utiliza, a versão atual do pacote exige que as JVMs dos clientes tenham acesso a dois arquivos: um contendo a classe original do objeto e um contendo a classe do proxy.

EXEMPLO:

Apesar do exemplo contradizer a última frase ao dizer que B consegue participar da aplicação sem ter os dois arquivos, repare que as instâncias de MyReference não poderão ser usadas por B. Ele precisa de um conhecimento mais profundo sobre quais métodos da classe MyObject requerem acesso de escrita, seus parâmetros, etc.

Por isso, a maneira adequada de construir uma aplicação distribuída em cima do pacote br.shmem é disponibilizando os arquivos .class de cada classe.

Um proxy deve promover acesso fácil aos métodos do objeto, como no exemplo abaixo:

class MyReference extends SharedReference {
  ... // vários métodos
  
  public java.awt.Color getColor() 
  {
    try {
      return (Color) super.callMethod("getColor", null, null);
    }
    catch (InvocationException e) {
      // never happens
    }
    catch (CommLayerException e) {
      // return new Color(255, 255, 255);
    }
    ... // mais catches
  }

  public void setColor(java.awt.Color newColor)
  throws ObjectLostException, CommLayerException, InsecureObjectException
  {  
     Class[] classList = new Class[1];
     Object[] objList = new Object[1];

     super.beginTrans();
     try {
        classList[0] = java.awt.Color.class;
        objList[0] = newColor;
        super.callMethod("setColor", classList, objList);
     }
     catch (InvocationException e) {
        System.err.println("Cannot set color for: " + getGlobalName() +
	                   ".\nReason: " e.getInner());
        super.rollback();
     }
     ... // mais catches
     super.commit();

  }

}

Este exemplo simples demonstra a utilidade do proxy. Muitas das Exceptions não foram tratadas por uma questão de simplicidade.