It's possible that multiple jobs in the system need to access and manipulate the same object. ILock defines protocol for granting exclusive access to a shared object. When a job needs access to the shared object, it acquires a lock for that object. When it is finished manipulating the object, it releases the lock.
A lock is typically created when the shared object is created or first accessed by a plug-in. That is, code that has a reference to the shared object also has a reference to its lock. We'll start by creating a lock, myLock, that will be used to control access to myObject:
... myObject = initializeImportantObject(); IJobManager jobMan = Job.getJobManager(); myLock = jobMan.newLock(); ...
A robust implementation of ILock is provided by the platform. The job manager provides instances of this lock for use by clients. These locks are aware of each other and can avoid circular deadlock.(We'll explain more about that statement in a moment.)
Whenever code in a job requires access to myObject, it must first acquire the lock on it. The following snippet shows a common idiom for working with a lock:
... // I need to manipulate myObject, so I get its lock first. try { myLock.acquire(); updateState(myObject); // manipulate the object } finally { lock.release(); } ...
The acquire() method will not return until the calling job can be granted exclusive access to the lock. In other words, if some other job has already acquired the lock, then this code will be blocked until the lock is available. Note that the code that acquires the lock and manipulates myObject is wrapped in a try block, so that the lock can be released if any exceptions occur while working with the object.
Seems simple enough, right? Fortunately, locks are pretty straightforward to use. They are also reentrant, which means you don't have to worry about your job acquiring the same lock multiple times. Each lock keeps a count of the number of acquires and releases for a particular thread, and will only release from a job when the number of releases equals the number of acquires.
Earlier we noted that locks provided by the job manager are aware of each other and can avoid circular deadlock. To understand how deadlock occurs, let's look at a simple scenario. Suppose "Job A" acquires "Lock A" and subsequently tries to acquire "Lock B." Meanwhile, "Lock B" is held by "Job B" which is now blocked waiting on "Lock A." This kind of deadlock indicates an underlying design problem with the use of the locks between the jobs. While this simple case can be avoided easily enough, the chances of accidentally introducing deadlock increase as the number of jobs and locks used in your design increase.
Fortunately, the platform will help you in identifying deadlocks. When the job manager detects a deadlock condition, it prints diagnostic information to the log describing the deadlock condition. Then it breaks the deadlock by temporarily granting access to the locks owned by a blocked job to other jobs that are waiting on them. It is important to carefully test any implementation involving multiple locks and fix any deadlock conditions that are reported by the platform.