服务器端并发控制 Server-side Concurrency Control

katomic I use Actionscript on the client side, and it's extremely easy as there is only one thread running (ignoring the system related threads like networking, etc). However, on the sever side, things are getting pretty complicated. Consider the following code:

public class ServiceCenter {

   private static PicklistsCache picklistCache;

   public static PicklistsCache getPicklistCache() {
      if(picklistCache == null) {
         picklistCache = new PicklistsCache(this);
      }
   return picklistCache;
}

There are multiple requests from the client side and multiple threads will call the getPicklistCache method. How many number of PicklistsCache will be created? Most people will answer one - which is incorrect. There could one one, two, or many more instances of PicklistsCache will be created. Why? Let's say there are 2 threads execute at both line 06, at this time the class variable picklistCache is null, so each thread will go ahead and create a PicklistsCache. Thus, two instances of PicklistsCache are created. In the end, the value of picklistCache will be overwritten by the slower thread.

To fix it, you simply use a synchronized block. Note, you need to re-check whether the variable is null - if 2 threads execute at line 7 below, one thread will be arrange to pass the synchronized block and the other will be queue to wait for the completion of the first. Of course, you can simply add synchronized to the method declaration, but doing so will hurt the performance.

public class ServiceCenter {

   private static PicklistsCache picklistCache;

   public PicklistsCache getPicklistCache() {
      if(picklistCache == null) {
         synchronized(ServiceCenter.class) {
            if(picklistCache == null) {
                picklistCache = new PicklistsCache(this);
            }
         }
      }
      return picklistCache;
   }
}

This kind of concurrency problems occur frequently on the sever side. You need to monitor closely to detect and fix them before getting bitten.

UPDATE: Oct 06, 2008 - concurrency bug fixed

// Must use volatile to avoid concurrency problem.
private volatile ToMany _relAttributesOwnedCached;
/** Fast look up map for attributes: cdName -> Attribute */
private volatile Map _attributesMap;

private void refreshAttributesMap() {
 if(_attributesMap == null) {
 	_attributesMap = new ConcurrentHashMap(64);
 }
 _attributesMap.clear();

 List attrs = getAttributesOwned();
 for(int i = 0; attrs != null && i < attrs.size(); i++) {
	Attribute attr = (Attribute) attrs.get(i);
	_attributesMap.put(attr.getSystemName(), attr);
 }
}

/**
* Finds the attribute with the given SQL column name (case-sensitive) - fast look up with Map.
* @param cdName the SQL column name for the attribute.
* @return the attribute with the given SQL column name or null if not found.
*/
public Attribute getAttributeBySystemName(String systemName) {
 ToMany attrsOwned = (ToMany) getRelationshipObjectOrCreateResolve(REL_attributesOwned, false);
 if(attrsOwned.isCommitted()) {
	if(_relAttributesOwnedCached != attrsOwned) {
		synchronized (this) {
				if(_relAttributesOwnedCached != attrsOwned) {
					refreshAttributesMap();			
					_relAttributesOwnedCached = attrsOwned;
				}
			}
	}

	if(_attributesMap.get(systemName) == null) {
		log.warn("getAttributeBySystemName(" + systemName + ") not found: - " + toString(true));
	}
	return _attributesMap.get(systemName);
 }else{
	...
 }
}

Two tips:

  • add volatile modifier to temporary/intermediate variables;
  • use synchronized block in conjunction with the volatile variables to avoid concurrency problems.