É possível que vários trabalhos no sistema precisem se aceder e manipular o mesmo objecto. O ILock define protocolo para conceder acesso exclusivo a um objecto partilhado. Quando um trabalho precisa de acesso ao objecto partilhado, adquire um bloqueio sobre esse objecto. Quando terminar de manipular o objecto, liberta o bloqueio.
Regra geral, cria-se um bloqueio quando o objecto partilhado é criado ou acedido pela primeira vez por um plug-in. Significa isto que o código que tem uma referência ao objecto partilhado também tem referência ao respectivo bloqueio. Começamos por criar um bloqueio, oMeuBloqueio, que será usado para controlar o acesso a oMeuObjecto:
... oMeuObjecto = initializeImportantObject(); IJobManager jobMan = Platform.getJobManager(); oMeuBloqueio = jobMan.newLock(); ...
A plataforma proporciona uma robusta implementação do ILock. OP gestor de trabalhos proporciona instâncias deste bloqueio para utilização dos clientes. Estes bloqueios conhecem-se entre si e podem evitar impasses circulares. (Explicaremos o que isto é daqui a pouco.)
Sempre que o código necessita de acesso a oMeuObjecto, primeiro deve adquirir um bloqueio sobre ele. A porção de código seguinte mostra uma maneira comum de trabalhar com um bloqueio:
... // Preciso de manipular oMeuObjecto, por isso adquiro primeiro o bloqueio. try { oMeuBloqueio.acquire(); updateState(oMeuObjecto); // manipular o objecto } finally { lock.release(); } ...
O método acquire() não devolve nada enquanto o trabalho de chamada não receber acesso exclusivo ao bloqueio. Por outras palavras, se outro trabalho já tiver adquirido o bloqueio, este código será bloqueado até o bloqueio estar disponível. Repare que o código que adquire o bloqueio e manipula oMeuObjecto está encapsulado num bloco try, de modo que o bloqueio pode ser libertado se ocorrerem excepções ao trabalhar com o objecto.
Parece simples, não é? Felizmente, os bloqueios são muito fáceis de utilizar. Também são reincidentes, o que significa que não é preciso preocupar-se com o trabalho a adquirir o mesmo bloqueio várias vezes. Cada bloqueio mantém uma contagem do número de aquisições e libertações de determinado módulo, e só se liberta de um trabalho quando o número de libertações equivaler ao número de aquisições.
Anteriormente notámos que os bloqueios proporcionados pelo gestor de trabalhos se conhecem entre si e podem evitar impasses circulares. Para compreender como se dá um impasse, vejamos um cenário simples. Suponhamos que o "Trabalho A" adquire o "Bloqueio A" e subsequentemente tenta adquirir o "Bloqueio B". Entretanto, o "Bloqueio B" é retido pelo "Trabalho B", o qual se encontra agora bloqueado à espera no "Bloqueio A". Este tipo de impasse indica um problema de concepção subjacente na utilização dos bloqueios entre trabalhos. Porquanto se possa evitar facilmente este caso simples, as hipóteses de entrar em impasses acidentais aumentam com o número de trabalhos e bloqueios usado na concepção.
Felizmente, a plataforma ajuda a identificar impasses. Quando o gestor de trabalhos detecta uma condição de impasse, imprime informações de diagnóstico no ficheiro de registo a descrever a condição de impasse. Em seguida dissolve o impasse mediante concessão de acesso temporário aos bloqueios pertencentes a um trabalho bloqueado sobre outros trabalhos que esperem neles. É importante testar cuidadosamente qualquer implementação que implique vários bloqueios e corrigir condições de impasse reportadas pela plataforma.