OverviewWith optimistic locking, you write your program under the assumption that an update operation has a chance of failing, if your updated object has been changed by someone else since you read it. Optimistic locking offers higher concurrency and better performance than pessimistic locking. It also avoids deadlocks. Optimistic data locking relies on the idea that data remains unmodified while it is away from the server. As a simple example, consider how you would update client details. The customer details are stored in an object, and if a client wants to update them, it will first need to get the object from the space. The data is not locked, and other clients can have access to it simultaneously, thus ensuring a scalable system. The problem is that while the customer details are away from the space server, they may become stale. For example, a second client can request the same customer details, update them, and commit them back to the space. The first client, unaware that it is dealing with a stale copy of the data, modifies and commits the data. Obviously, with no checking mechanism to detect this conflict, the first client's changes, which commit last, will be made permanent, thus overwriting the changes made by the second client. Optimistic locking is best suited for environments with many read-only transactions, few read-update transactions, and a relatively low volume of objects that are changed. Pessimistic locking may be less suitable for real-time systems than optimistic locking, because the space runs best with short term transactions. Optimistic locking also has a big advantage when you may want to read a large number of objects but update only a few of them; or when it is unlikely that objects you want to work with will be updated by others. In general, optimistic locking ensures that updated objects are the most recent ones, while improving the coherency of system behavior. Optimistic Locking ActionsTo get optimistic locking features to work with unlocked objects:
By following the above procedure, you get a shorter locking duration, which improves performance and concurrency of access among multiple users. At the same time, the version ID validation performed on update, take, and multipleUpdate requests keeps the space consistent. Making Class Optimistic Locking Protocol EnabledEntry Based ClassAn object is in optimistic locking protocol mode and its version information is updated when:
EntryInfo Access MethodsTo make a class "optimistic locking ready," add the _setEntryInfo and _getEntryInfo methods to the class. public class Employee implements Entry{ transient private EntryInfo m_EntryInfo;// <- stores the EntryInfo data public String name; public Integer age; public void __setEntryInfo(EntryInfo entryInfo) { m_EntryInfo = entryInfo; } public EntryInfo __getEntryInfo() { return m_EntryInfo; } public Employee() {} public Employee( String name , Integer age ) { this.age = age; this.name = name; } } The EntryInfo class includes the following attributes:
If an object has the EntryInfo access methods, the GigaSpaces take, update, and updateMultiple automatically use the version ID to detect obsolete copies in the space, and prevent you from using the obsolete copies as the basis for changing the object. If you use a template for the take operation that includes EntryInfo access methods, the system checks the template's version ID. If it is not zero, and does not match the version ID of the Entry in the space, the take operation fails. You can avoid obsolete copies by first refreshing (reading) every object you want to change before performing an Update operation. This is costly, because objects must be fetched and because of the time required to make detailed evaluations of every attribute in every object. Once you add EntryInfo access methods to an object, the version ID is updated, no matter who does the updating. When an entry with EntryInfo access methods is created, its VersionID attribute is set to 1. Thereafter, each update increments the VersionID attribute by 1 (during the update or the multipleUpdate operation at the space side). POJO Based ClassThe POJO Class should have the @SpaceVersion field level decoration to accommodate the version ID. @SpaceClass(replicate=true,persist=false,fifo=false) public class Employee { private Integer employeeID; private String lastName; private String firstName; private int versionID; public Employee() { } public Employee(Integer employeeID) { this.employeeID = employeeID; } public Employee(String lastName, Integer employeeID) { this.employeeID = employeeID; setLastName(lastName); } public Employee(String lastName,String firstName ,Integer employeeID) { this.employeeID = employeeID; setFirstName(firstName); setLastName(lastName); } @SpaceId @SpaceProperty(index=IndexType.BASIC) public Integer getEmployeeID() { return employeeID; } public void setEmployeeID(Integer employeeID) { this.employeeID = employeeID; } @SpaceProperty(index=IndexType.BASIC) private String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } private String getLastName() { return lastName; } public void setLastName(String name) { this.lastName = name; } public void setVersionID(int versionID) { this.versionID= versionID; } @SpaceVersion public int getVersionID() { return versionID; } public boolean equals(Object other) { if (!(other instanceof Employee)) return false; else { Employee otherBean = (Employee) other; return ((otherBean.getEmployeeID() != null && otherBean.getEmployeeID().equals(getEmployeeID()) || otherBean.getEmployeeID() == getEmployeeID())); } } public String toString() { return " \tlastName: " + lastName + ", \tfirstName: " + firstName + ", \temployeeID: "+ employeeID; } } Here is a simple example illustrating EntryVersionConflictException usage: IJSpace space = (IJSpace) SpaceFinder.find("jini://localhost/*/mySpace?versioned=true"); Employee employ1= new Employee("Last Name1" , "First name1" , new Integer(1)); space.write(employ1, null, Lease.FOREVER); Employee temp = new Employee("Last Name1" , "First name1" , new Integer(1)); try { Employee result1 = (Employee) space.read(temp , null, 0); Employee result2 = (Employee) space.read(temp , null, 0); System.out.println("About to Write Version:" + result1.getVersionID()); space.write(result1, null, Lease.FOREVER); System.out.println("Employee ID " + result1.getEmployeeID() + " - Entry version in Space:" + result2.getVersionID()); System.out.println("About to Write Version:" + result2.getVersionID()); space.update(result2, null, 0,0); } catch (com.j_spaces.core.client.EntryVersionConflictException evce) { System.out.println("Got " + evce ); System.out.println("re-read Object again"); Employee result3 = (Employee) space.read(temp , null, 0); System.out.println("perform Update again.. Client Entry version:" + result3.getVersionID()); Employee prevValue= (Employee)space.update(result3, null, 0,0); if (prevValue != null) System.out.println("Update Successful! Entry version in Space:" + result3.getVersionID()); } Here is the expected output: About to Write Version:1 Employee ID 1 Entry Version in Space:1 About to Write Version1 Got EntryVersionConflictException com.j_spaces.core.client.EntryVersionConflictException: Entry Version ID conflict, Operation rej ected. Operation=Update UID=-1786435842^45^1^0^0 space entry version=2 client version=1 Update sucessful When you detect an obsolete copy, you can decide what to do. You may want to refresh your copy of the object by re-reading it, inspecting it, and then reapplying some or all of your modifications; or you may simply want to abandon your update by calling the abort () method. GigaSpaces Optimistic Locking ProtocolWhen using optimistic locking, you must take responsibility for coherence and consistency of the space Entry objects by following an optimistic locking protocol. Step 1 – Get Space ProxyYou should first get the space proxy using the SpaceFinder. You may get remote or embedded space proxies. Make sure the proxy is in optimistic locking mode. Step 2 – Read Objects with null TransactionRead objects from the space with a null transaction. You may use the readMultiple () method to get several objects in one call. Step 3 – Open Transaction (optional)Use the usual TransactionManager () method to start a transaction. Step 4 – Browse, Modify, and UpdateModify the objects you read from the space and call the update or the updateMultiple () space operation.
Step 5 – Update FailureIf you use optimistic locking and your update operation fails, you should get the EntryVersionConflictException. This exception is thrown when you try to write an Entry to the space during an update operation, and its version ID does not match the version of the existing Entry. In other words, when you are not using the latest version. If some objects have outdated version IDs or have been deleted by another user, you can either roll back or refresh the failed objects and try again. Rolling back the transaction restores the objects to their values before they were modified at the space side. Refreshing the failed objects reads the latest committed objects from the server to the client side. For fast refresh, you may read the objects using their UIDs.
Step 6 – Commit or Rollback ChangesAt any time, you can save or roll back your updates with a transaction commit or rollback. Optimistic Locking with a Single ObjectWith optimistic locking, you write your program under the assumption that an update operation has a chance of failing, if your updated object was changed by someone else since you read it. With optimistic locking set, an update operation has a chance of failing (throwing an EntryVersionConflictException ) if it was performed on an obsolete copy.
Optimistic Locking with Multiple ObjectsThe Quick Start Guide contains an example that demonstrates an optimistic locking protocol with the updateMultiple() method. In comparison to the previous example, an update is considered successful if and only if all Entries were updated. Otherwise, the transaction should be aborted. In general, when calling updateMultiple() you should check every returned object from the array returned from the updateMultiple() method. If one of the returned objects indicates that the update failed (eg. EntryVersionConflictException ), you need to abort the transaction, re-read, and update again. After a successful update, the Entry will include both the updated Entry information and the updated version ID. VersioningVersioning addresses the issue of concurrent updates on the same object among multiple cache instances. When running in optimistic locking mode, the space uses a versioning mechanism to ensure the coherency of the updated data and to synchronize concurrent updates. How it WorksThe following diagram illustrates versioned cache flow. In this diagram, we use two cache instances: cache1 and cache2. Initially, both cache instances contain the same object instance with the same version. Cache1 updates the object in the cache successfully. As a result of this operation, the version of the object is updated to 1. Cache2 attempts to update the same object with an older version of this object. The cache detects this conflict and throws a runtime exception that indicates a version conflict. In order for Cache2 to succeed, it needs to read the updated version from the master cache, and then repeat the update operation. Versioned Cache RestrictionsSome restrictions apply when using the Map interface:
Scenario ExampleSuppose that you have two applications, Application_1 and Application_2, which are both working with the same Object A. The following sequence of events describes a simple optimistic locking scenario.
Code ExampleThis example demonstrates the usage of optimistic lock protocol. One Entry is updated by many concurrent workers. Every worker updates the salary field by 10. The internal versionID is used to keep consistency. A running example can be found at: Steps to run the example:
Application Source CodeThe Entry class – extends the MetaDataEntry: package com.j_spaces.examples.data_grid.optimistic_lock; import com.j_spaces.core.client.MetaDataEntry; public class myEntry extends MetaDataEntry { public String name; public Integer salary; public myEntry() { } public myEntry( String name, Integer salary ) { this.name = name; this.salary = salary; } public void increaseSalary() { int updatedSalary = salary.intValue() + 10; salary = new Integer( updatedSalary ); } } The application: package com.j_spaces.examples.data_grid.optimistic_lock; import java.util.GregorianCalendar; import net.jini.core.lease.Lease; import net.jini.space.JavaSpace; import com.j_spaces.core.IJSpace; import com.j_spaces.core.client.EntryVersionConflictException; import com.j_spaces.core.client.EntryNotInSpaceException; import com.j_spaces.core.client.FinderException; import com.j_spaces.core.client.MetaDataEntry; import com.j_spaces.core.client.SpaceFinder; public class OptimisticLock { int numOfWorkers; IJSpace spaceProxy; class Worker extends Thread { myEntry workerEntry; public void say (String msg) { java.util.GregorianCalendar now = new java.util.GregorianCalendar(); String t = now.get(GregorianCalendar.HOUR_OF_DAY)+":"+ now.get(GregorianCalendar.MINUTE)+":"+ now.get(GregorianCalendar.SECOND)+":"+ now.get(GregorianCalendar.MILLISECOND) ; System.out.println( getName() + " (" + t + ")" + msg); } Worker(String workerName ) throws Exception { this.setName( workerName ); } public void run() { try { while(true) { try { workerEntry = (myEntry)spaceProxy.read(new myEntry(),null, JavaSpace.NO_WAIT ); workerEntry.increaseSalary(); say(" Before update: UID " +workerEntry.__getEntryInfo().m_UID+ " VersionID " + workerEntry.__getEntryInfo().m_VersionID); myEntry oldWorkerEntry = (myEntry)spaceProxy.update( workerEntry, null, Lease.FOREVER, JavaSpace.NO_WAIT ); say(" After update: UID " + workerEntry.__getEntryInfo().m_UID+ " VersionID " + workerEntry.__getEntryInfo().m_VersionID + " - Update OK!" ); /* * Update returned object could be: * The old Entry - Update Ok * Null - In case the entry is blocked by other user - Update failed * * If there was problem with the update operation the following * Exception might be thrown: * * EntryNotInSpaceException - in case the entry does not exists in * space - Update failed * * EntryVersionConflictException - In case updating non-latest * version - Update failed * * After successful update the entry will include updated Entry * info with updated version ID. */ }catch(Exception ex) { if ( ex instanceof EntryNotInSpaceException ) { EntryNotInSpaceException enise = (EntryNotInSpaceException)ex; say(" Update Failed - entry Not In Space - " +enise); } else if ( ex instanceof EntryVersionConflictException ) { EntryVersionConflictException evce = (EntryVersionConflictException)ex; say(" Update Failed - using old worker info - " +evce+ "- re-read entry and update..." ); continue; } else say(" failed: " + ex.getMessage() ); } return; }// while.. }catch(Exception ex) { say(" failed: " + ex.getMessage() ); } } }// end class public OptimisticLock(String spaceUrl, int numOfWorkers) throws Exception { this.numOfWorkers = numOfWorkers; spaceProxy = (IJSpace)SpaceFinder.find( spaceUrl ); spaceProxy.clear( null, null ); } public void go() throws Exception { System.out.println("\nWelcome to GigaSpaces Optimistic Locking example"); System.out.println("This will update an entry by multiple threads "+ "using optimistic locking protocol"); myEntry we = new myEntry( "Dave", new Integer(3000)); System.out.println("\nEntryname: " + we.name + "\nSalary: " + we.salary + "\nNumber of Workers: " + numOfWorkers); System.out.println("Each worker is increasing Dave's salary by 10...\n"); spaceProxy.write( we, null, Lease.FOREVER ); // initilize workers Worker[] workers = new Worker[numOfWorkers]; for (int i = 0; i < workers.length; i++) workers[i] = new Worker("Worker" + i); // start workers for (int i = 0; i < workers.length; i++) workers[i].start(); // wait while all workers finished for (int i = 0; i < workers.length; i++) workers[i].join(); // check salary we = (myEntry)spaceProxy.read( new myEntry(),null,JavaSpace.NO_WAIT ); System.out.println("\nEntryname: "+we.name+"\nSalary: "+we.salary); } public static void main(String[] args) throws Exception { if ( args.length != 2 ) { System.out.println("Usage: <spaceURL> <Num_of_Workers>"); System.out.println("<protocol>://host:port/containername/spacename"); System.exit(1); } try { OptimisticLock ol = new OptimisticLock(args[0],Integer.parseInt(args[Look And Feel - ServiceGrid])); ol.go(); System.exit(0); } catch( FinderException ex ) { ex.printStackTrace(); System.out.println("Could not find space: " + args[0]); System.out.println("Please check that GigaSpaces Server is running."); } } } |
![]() |
GigaSpaces.com - Legal Notice - 3rd Party Licenses - Site Map - API Docs - Forum - Downloads - Blog - White Papers - Contact Tech Writing - Gen. by Atlassian Confluence |