Browse DevX
Sign up for e-mail newsletters from DevX


Use Callbacks to Isolate Concurrency Bugs : Page 3

By restricting all object data access to a callback mechanism, a Java server can contain all concurrency issues in a single place, making it much easier for you to see if concurrency constraints have been violated.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Implementing use()

Take a closer look at the use() method of the GateKeeper class, as this is where the action is. Note that the parameter to use() is of type User, which means that you can pass in an object that is an Accessor, a Mutator, or both:

public void use( User user ) {

Since the user could be both a Mutator and an Accessor, you need to figure out which aspect of it you want to take care of first. For this tutorial, mutate first:

if (user instanceof Mutator) { Mutator mutator = (Mutator)user;

You know now that the user is a Mutator. But before you let the Mutator do its mutating, you need to acquire a lock. In fact, you will wrap the mutation activity inside a lock/unlock pair:

try { rwlock.getWriteLock(); // LOCK mutator.mutate( o ); } finally { rwlock.releaseWriteLock(); // UNLOCK }

You see here that, deep down, the callback method isn't fundamentally different from the traditional lock/access/unlock pattern. However, the locking is done entirely by the gatekeeper. This has a number of significant benefits:

  1. The structure of the locking and unlocking is very clear.
  2. The GateKeeper can determine the locking policy.
  3. The client code is much simpler.
  4. The locking and unlocking happens in only one place.

For high-availability servers, the fourth benefit is perhaps the most important. Normally, every client of a multi-threaded data structure takes care of locking and unlocking, and so each one can potentially lock a resource and forget to unlock it. As a programmer, you try to avoid this, but it still happens sometimes. If "sometimes" is too often for your highly available server, then you must provide a stronger guarantee.

The code fragment above uses a finally block to ensure that the resource is unlocked no matter what happens.

Now that you have let the Mutator mutate, you should allow the Accessor to access. You allowed the Mutator to change the data, but allow the Accessor only to read it:

if (user instanceof Accessor) { Accessor accessor = (Accessor)user;

This code is very similar to the mutation code above. The difference is that you need only a read lock, not a write lock:

try { rwlock.getReadLock(); accessor.access( o ); } finally { rwlock.releaseReadLock(); }

Since a read lock is not exclusive, this configuration allows many readers to read the data simultaneously. At the same time, a thread that wants to write to the data has absolutely exclusive access.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date