Benefits of Callbacks
Implementing a callback-based system requires extra work but it offers a number of benefits.
Normally, you must acquire certain locks before you can access certain data. Synchronized methods can make this mandatory, but they often aren't enough because the accessing code still has to obey some kind of synchronization protocol. As a system gets larger, you are more and more liable to access the data at the wrong time. Also, just putting synchronization blocks around everything becomes more likely to result in deadlock.
A callback, on the other hand, cannot access the data until that data is passed to the method, and it cannot access the data after the method is finished. Thus, the period in which the data is available is precisely delineated.
Furthermore, this mechanism is controlled by the gatekeeper, which can implement any kind of ordering mechanism. Conversely, the traditional wait/notify method provides no way of knowing which threads will gain access at which times.
(Of course, a callback method is free to squirrel away a pointer to the sensitive data, or pass it to another object or thread. This would certainly violate the linearity guarantee, but it is not the kind thing you are liable to do accidentally.)
The Sum Class
To initiate your design, create the sensitive data object first. Use a very simple data object called a Sum:
public class Sum
public int a, b, c;
A sum stores three numbers: a, b, and c, where c = a + b. However, these variables are public, which means someone could modify them so that c is no longer equal to a + b. This is the problem you want to avoid.
Of course, you easily could protect c by hiding it behind a synchronized access method, but as mentioned previously, this article's purpose is to explore an alternative method of data protection, one you would use when regular synchronization is either difficult or undesirable. Sum is a trivial object, but the complexity of the vulnerable data isn't the focus here. Rather, the vulnerability itself is.
The GateKeeper Class
All access to the Sum object will go through the GateKeeper class. A GateKeeper controls access to the object that is passed to its constructor:
GateKeeper gk = new GateKeeper( new Sum() );
To use the object hidden inside the GateKeeper, you must pass a callback to the GateKeeper's
gk.use( user );
The User, Accessor, and Mutator Interfaces
Java does not have first-class functions, so you can't use actual callbacks. However, you can get close to real callbacks by using interfaces.
The Accessor interface describes an object that wants to read a piece of data:
public interface Accessor extends User
public void access( Object o );
Similarly, the Mutator interface describes an object that wants to write to a piece of data:
public interface Mutator extends User
public void mutate( Object o );
You also need to define an empty interface called User. Objects that implement User are either Accessors or Mutators:
public interface User
For good measure, you also have the interface MutatingAccessor, which implements both Accessor and Mutator. This isn't strictly necessary, but it makes your object declarations neater:
public interface MutatingAccessor extends Accessor, Mutator