Es posible que varios trabajos del sistema necesiten acceder al mismo objeto y manipularlo. ILock define un protocolo para otorgar acceso exclusivo a un objeto compartido. Cuando un trabajo necesita acceso al objeto compartido, adquiere un bloqueo sobre ese objeto. Cuando ha terminado de manipular el objeto, libera el bloqueo.
Generalmente, un bloqueo se crea cuando se crea el objeto compartido o cuando un conector accede a él por primera vez. Es decir, el código que contiene una referencia al objeto compartido también contiene una referencia a su bloqueo. Empezaremos por crear un bloqueo, myLock, que se utilizará para controlar el acceso a myObject:
... myObject = initializeImportantObject(); IJobManager jobMan = Platform.getJobManager(); myLock = jobMan.newLock(); ...
La plataforma suministra una implementación robusta de ILock. El gestor de trabajos proporciona instancias de este bloqueo para que las utilicen los clientes. Estos bloqueos tiene conocimiento de su mutua existencia y pueden evitar puntos muertos circulares (dentro de poco describiremos este fenómeno con mayor detalle).
Cuando el código de un trabajo requiere acceso a myObject, primero debe adquirir el bloqueo sobre dicho objeto. El siguiente fragmento de código muestra un idioma común para trabajar con un bloqueo:
... // Necesito manipular myObject, por tanto obtengo primer el bloqueo. try { myLock.acquire(); updateState(myObject); // manipular el objeto } finally { lock.release(); } ...
El método acquire() no efectuará el retorno hasta que el trabajo llamante pueda obtener acceso exclusivo al bloqueo. En otras palabras, si algún otro trabajo ya ha adquirido el bloqueo. este código quedará bloqueado hasta que el bloqueo esté disponible. Tenga en cuenta que el código que adquiere el bloqueo y manipula myObject se agrupa en un bloque try, de forma que el bloqueo puede liberarse si se produce alguna excepción al trabajar con el objeto.
Parece bastante sencillo, ¿no? Afortunadamente, la utilización de los bloqueos es bastante directa. También son reentrantes, lo que significa que no es necesario preocuparse de si el trabajo adquiere el mismo bloqueo varias veces. Cada bloqueo mantiene una cuenta del número de adquisiciones y liberaciones de una hebra determinada, y sólo se liberará de un trabajo cuando el número de liberaciones sea igual al número de adquisiciones.
Anteriormente se ha mencionado que los bloqueos suministrados por el gestor de trabajos tienen conocimiento de su mutua existencia y pueden evitar el punto muerto circular. Para entender cómo se produce un punto muerto, observemos un caso práctico sencillo. Supongamos que el "Trabajo A" adquiere el "Bloqueo A" y a continuación intenta adquirir el "Bloqueo B". Entretanto, el "Bloqueo B" queda retenido por el "Trabajo B", que ahora queda bloqueado en espera del "Bloqueo A". Este tipo de punto muerto indica que existe un problema de diseño subyacente en la utilización de los bloqueos entre los trabajos. Aunque este caso sencillo puede evitarse bastante fácilmente, las posibilidades de introducir accidentalmente un punto muerto aumentan a medida que lo hace el número de trabajos y bloqueos utilizados en el diseño.
Afortunadamente, la plataforma la ayudará a identificar los puntos muertos. Cuando el gestor de trabajos detecta una condición de punto muerto, registra en las anotaciones información de diagnóstico que describe la condición de punto muerto. A continuación, rompe el punto muerto otorgando acceso temporal a los bloqueos que son propiedad de un trabajo bloqueado a otros trabajos que los esperan. Es importante probar cuidadosamente cualquier implementación que implique varios bloqueos y solucionar las condiciones de punto muerto notificadas por la plataforma.