Optimistic Locking

In optimistic locking system no locks are used to prevent collision: any user can read an object into the memory and work on it at any time. However, before the client can save its modifications back to the database, a check should take place verifying that the item did not change since the time of initial read (no collision occurred). If a collision is detected it should be resolved according to your application logic. Typical solutions are:

Let's look at an example realization.

We will use a db4o database containing objects of Pilot class and a separate thread to create a client connection to the database, retrieve and modify objects.

OptimisticThread.java: run
01public void run() { 02 try { 03 ObjectSet result = _db.get(Pilot.class); 04 while (result.hasNext()){ 05 Pilot pilot = (Pilot)result.next(); 06 long objVersion = _db.ext().getObjectInfo(pilot).getVersion(); 07 08 /* save object version into _idVersions collection 09 * This will be needed to make sure that the version 10 * originally retrieved is the same in the database 11 * at the time of modification 12 */ 13 long id = _db.ext().getID(pilot); 14 _idVersions.put(id, objVersion); 15 16 System.out.println(getName() + "Updating pilot: " + pilot+ " version: "+objVersion); 17 pilot.addPoints(1); 18 _updateSuccess = false; 19 randomWait(); 20 if (!_db.ext().setSemaphore("LOCK_"+_db.ext().getID(pilot), 3000)){ 21 System.out.println("Error. The object is locked"); 22 continue; 23 } 24 _db.set(pilot); 25 /* The changes should be committed to be 26 * visible to the other clients 27 */ 28 _db.commit(); 29 _db.ext().releaseSemaphore("LOCK_"+_db.ext().getID(pilot)); 30 if (_updateSuccess){ 31 System.out.println(getName() + "Updated pilot: " + pilot); 32 } 33 System.out.println(); 34 /* The object version is not valid after commit 35 * - should be removed 36 */ 37 _idVersions.remove(id); 38 } 39 40 } finally { 41 _db.close(); 42 } 43 }

A semaphore is used for locking the object before saving and the lock is released after commit when the changes become visible to the other clients. The semaphore is assigned a name based on object ID to make sure that only the modified object will be locked and the other clients can work with the other objects of the same class simultaneously.

Locking the object for the update only ensures that no changes will be made to the object from the other clients during update. However the object might be already changed since the time when the current thread retrieved it. In order to check this we will need to implement an event handler for the updating event:

OptimisticThread.java: registerCallbacks
01public void registerCallbacks(){ 02 EventRegistry registry = EventRegistryFactory.forObjectContainer(_db); 03 // register an event handler to check collisions on update 04 registry.updating().addListener(new EventListener4() { 05 public void onEvent(Event4 e, EventArgs args) { 06 CancellableObjectEventArgs queryArgs = ((CancellableObjectEventArgs) args); 07 Object obj = queryArgs.object(); 08 // retrieve the object version from the database 09 long currentVersion = _db.ext().getObjectInfo(obj).getVersion(); 10 long id = _db.ext().getID(obj); 11 // get the version saved at the object retrieval 12 long initialVersion = ((Long)_idVersions.get(id)).longValue(); 13 if (initialVersion != currentVersion){ 14 System.out.println(getName() +"Collision: "); 15 System.out.println(getName() +"Stored object: version: "+ currentVersion); 16 System.out.println(getName() +"New object: " + obj+ " version: "+ initialVersion); 17 queryArgs.cancel(); 18 } else { 19 _updateSuccess = true; 20 } 21 } 22 }); 23 }

In the above case the changes are discarded and a message is sent to the user if the object is already modified from another thread. You can replace it with your own strategy of collision handling.

Note: the supplied example has random delays to make the collision happen. You can experiment with the delay values to see different behavior.

OptimisticThread.java: randomWait
1private void randomWait() { 2 try { 3 Thread.sleep((long)(5000*Math.random())); 4 } catch(InterruptedException e) { 5 System.out.println("Interrupted!"); 6 } 7 }