Última Atualização: 02 de Outubro de 2002 00:41 BRST
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 SharedSpace (br.shob.SharedSpace). 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 SharedSpace 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 SharedSpace 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.shob.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 SharedSpace 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.
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 SharedSpace 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.
A partir da versão 0.5, há um mecanismo muito simples de atualização das cópias no cache. A cada segundo, uma thread testa quais leases expiraram, pedindo uma verificação da cópia no cache.
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. Como as alterações são feitas diretamente na cópia no cache, outras threads na mesma máquina podem ler dados inconsistentes. Em vez de forçar a implementação de métodos atômicos no objeto compartilhado, demos as operações freeze e defrost ao programador.
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 consistentes, como veremos a seguir.
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 ou subclasse de SharedReference é usada como proxy para acessar os métodos daquele objeto.
Um proxy deve promover acesso fácil aos métodos do objeto, como no exemplo abaixo:
public class MyReference extends DefaultSharedReference {
//...varios metodos
public java.awt.Color getColor()
throws ShobException
{
try {
return (Color) super.srCallMethod("getColor", null, null);
}
catch (InvocationException e) {
// never happens
}
catch (InvocationTargetException e) {
return new Color(255, 255, 255);
}
}
public void setColor(java.awt.Color newColor)
throws ShobException
{
Class[] classList = new Class[1];
Object[] objList = new Object[1];
super.srBeginTrans();
try {
classList[0] = java.awt.Color.class;
objList[0] = newColor;
super.srCallMethod("setColor", classList, objList);
}
catch (InvocationException e) {
// never happens
}
catch (InvocationTargetException e) {
System.err.println("Cannot set color for: " + getGlobalName() +
".\nReason: " e.getInner());
super.srRollback();
}
... // mais catches
super.srCommit();
}
}
|
Este exemplo simples demonstra a utilidade do proxy.
Para eliminar a necessidade de contruir proxies manualmente, deve-se usar o gerador de proxies que é distribuído junto com o produto.