You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-dev@axis.apache.org by ba...@apache.org on 2008/10/09 20:00:33 UTC

svn commit: r703219 - in /webservices/axis2/trunk/java/modules/kernel: src/org/apache/axis2/context/ test/org/apache/axis2/context/

Author: barrettj
Date: Thu Oct  9 11:00:33 2008
New Revision: 703219

URL: http://svn.apache.org/viewvc?rev=703219&view=rev
Log:
Replace usage of ConcurrentHashMap with a collection that can be externally locked for update to prevent ConcurrentModificationExceptions.  The ConcurrentHashMap degraded performance by over 5% in some scenarios.

Modified:
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java
    webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java
    webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java Thu Oct  9 11:00:33 2008
@@ -28,16 +28,11 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import java.util.AbstractSet;
-import java.util.Collection;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * This is the top most level of the Context hierarchy and is a bag of properties.
@@ -58,7 +53,6 @@
     protected long lastTouchedTime;
 
     protected transient AbstractContext parent;
-
     protected transient Map properties;
     private transient Map propertyDifferences;
 
@@ -112,7 +106,15 @@
      */
     public Iterator getPropertyNames() {
         initPropertiesMap();
-        return properties.keySet().iterator();
+        HashSet copyKeySet = null;
+        // Lock the table in a try/catch so it will always be unlocked in the finally block
+        try {
+            ((HashMapUpdateLockable) properties).lockForUpdate();
+            copyKeySet = new HashSet(properties.keySet());
+        } finally {
+            ((HashMapUpdateLockable) properties).unlockForUpdate();
+        }
+        return (copyKeySet != null) ? copyKeySet.iterator() : null;
     }
 
     /**
@@ -330,7 +332,7 @@
                 // The Map we got argument is probably NOT an instance of the Concurrent 
                 // map we use to store properties, so create a new one using the values from the
                 // argument map.
-                this.properties = new ConcurrentHashMapNullSemantics(properties);
+                this.properties = new HashMapUpdateLockable(properties);
             }
         }
     }
@@ -426,262 +428,123 @@
             // This needs to be a concurrent collection to prevent ConcurrentModificationExcpetions
             // for async-on-the-wire.  It was originally: 
 //            properties = new HashMap(DEFAULT_MAP_SIZE);
-            properties = new ConcurrentHashMapNullSemantics(DEFAULT_MAP_SIZE);
+            properties = new HashMapUpdateLockable(DEFAULT_MAP_SIZE);
         }
     }
 }
 
 /**
- * ConcurrentHashMap that supports the same null semantics of HashMap, which means allowing null
- * as a key and/or value.  ConcurrentHashMap throws an NullPointerException if either of those
- * are null.  This is done by representing null keys and/or values with a non-null value stored in
- * the collection and mapping to actual null values for the methods such as put, get, and remove.
- * 
- * @param <K> Key type
- * @param <V> Value type
+ * HashMap that supports an update lock.  HashMap methods that would directly update the collection
+ * will block if the collection is locked.  They will be released when the collection is unlocked.
+ * Methods that do not update the collection will not be blocked.
  */
-class ConcurrentHashMapNullSemantics<K,V> extends ConcurrentHashMap<K,V> {
+class HashMapUpdateLockable extends HashMap {
 
-    private static final long serialVersionUID = 3740068332380174316L;
-    
-    // Constants to represent a null key or value in the collection since actual null values 
-    // will cause a NullPointerExcpetion in a ConcurrentHashMap collection.
-    private static final Object NULL_KEY_INDICATOR = new Object();
-    private static final Object NULL_VALUE_INDICATOR = new Object();
-    
-    public ConcurrentHashMapNullSemantics() {
+    // Used for synchronization during update locking.  Also indicates if the table is locked.
+    // Initially, it is not locked.
+    private UpdateLock updateLock = new UpdateLock(false);
+
+    HashMapUpdateLockable() {
         super();
     }
-    public ConcurrentHashMapNullSemantics(int initialCapacity) {
-        super(initialCapacity);
+    HashMapUpdateLockable(int size) {
+        super(size);
     }
 
-    public ConcurrentHashMapNullSemantics(Map<? extends K, ? extends V> map) { 
+    HashMapUpdateLockable(Map map) {
         super(map);
     }
-    
-    /**
-     * Similar to ConcurrentHashMap.put except null is allowed for the key and/or value.
-     * @see java.util.concurrent.ConcurrentHashMap#put(java.lang.Object, java.lang.Object)
-     */
-    public V put(K key, V value) {
-        return (V) valueFromMap(super.put((K) keyIntoMap(key), (V) valueIntoMap(value)));
-    }
-    
-    /**
-     * Similar to ConcurrentHashMap.get except null is allowed for the key and/or value.
-     * @see java.util.concurrent.ConcurrentHashMap#get(java.lang.Object)
-     */
-    public V get(Object key) {
-        return (V) valueFromMap(super.get(keyIntoMap(key)));
-    }
-        
+
     /**
-     * Similar to ConcurrentHashMap.remove except null is allowed for the key and/or value.
-     * @see java.util.concurrent.ConcurrentHashMap#remove(java.lang.Object)
+     * Similar to super method but will block if collection is locked.
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
      */
-    public V remove(Object key) {
-        // If the key is null, then look for the null key constant value in the table.
-        return (V) valueFromMap(super.remove(keyIntoMap(key)));
+    public Object put(Object key, Object value) {
+        checkUpdateLock(true);
+        return super.put(key, value);
     }
-    
-    /**
-     * Similar to entrySet EXCEPT (1) nulls are allowed for keys and values and (2) this 
-     * does NOT RETURN a LIVE SET.  Any changes made to the returned set WILL NOT be reflected in 
-     * the underlying collection.  Also, any changes made  to the collection after the set is 
-     * returned WILL NOT be reflected in the set.  This method returns a copy of the entries in the 
-     * underlying collection at the point it is called.  
-     * 
-     * @see java.util.concurrent.ConcurrentHashMap#entrySet()
-     */
-    public Set<Map.Entry<K,V>> entrySet() { 
-        // The super returns a ConcurrentHashMap$EntrySet
-        Set<Map.Entry<K, V>> collectionSet = super.entrySet();
-        Set<Map.Entry<K, V>> returnSet = new HashSet<Map.Entry<K, V>>();
-        Iterator<Map.Entry<K, V>> collectionSetIterator = collectionSet.iterator();
-        // Go through the set that will be returned mapping keys and values back to null as
-        // appropriate.
-        while (collectionSetIterator.hasNext()) {
-            Map.Entry entry = collectionSetIterator.next();
-            Object useKey = keyFromMap(entry.getKey());
-            Object useValue = valueFromMap(entry.getValue());
-            Map.Entry<K, V> returnEntry = new SetEntry<K, V>((K) useKey, (V) useValue);
-            returnSet.add(returnEntry);
-        }
-        return returnSet;
-    }
-    
+
     /**
-     * Similar to keySet EXCEPT (1) nulls are allowed for keys and (2) this 
-     * does NOT RETURN a LIVE SET.  Any changes made to the returned set WILL NOT be reflected in 
-     * the underlying collection.  Also, any changes made  to the collection after the set is 
-     * returned WILL NOT be reflected in the set.  This method returns a copy of the keys in the 
-     * underlying collection at the point it is called.  
-     * 
-     * @see java.util.concurrent.ConcurrentHashMap#keySet()
+     * Similar to super method but will block if collection is locked.
+     * @see java.util.HashMap#putAll(java.util.Map)
      */
-    public Set<K> keySet() {
-        Set<K> keySet = new SetKey<K>(super.keySet());
-        return keySet;
+    public void putAll(Map map) {
+        checkUpdateLock(true);
+        super.putAll(map);
     }
 
     /**
-     * Similar to containsKey except a null key is allowed.
-     * 
-     * @see java.util.concurrent.ConcurrentHashMap#containsKey(java.lang.Object)
+     * Similar to super method but will block if collection is locked.
+     * @see java.util.HashMap#remove(java.lang.Object)
      */
-    public boolean containsKey(Object key) {
-        return super.containsKey(keyIntoMap(key));
+    public Object remove(Object key) {
+        checkUpdateLock(true);
+        return super.remove(key);
     }
 
-    // REVIEW: Some of these may already work.  Any that are using put or get to access the elements
-    // in the Map will work.  These are not currently used for AbstractContext properties, so there 
-    // are no tests yet to verify they work (except as noted below)
-    public boolean contains(Object value) {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public boolean containsValue(Object value) {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public Enumeration<V> elements() {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public Enumeration<K> keys() {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    // Note that putAll(..) works for nulls because it is using put(K,V) for each element in the
-    // argument Map.
-//    public void putAll(Map<? extends K, ? extends V> t) {
-//        throw new UnsupportedOperationException("Not implemented for null semantics");
-//    }
-    public V putIfAbsent(K key, V value) {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public boolean remove(Object key, Object value) {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public boolean replace(K key, V oldValue, V newValue) {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public V replace(K key, V value) {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    public Collection<V> values() {
-        throw new UnsupportedOperationException("Not implemented for null semantics");
-    }
-    
     /**
-     * Returns a key that can be set into the collection.  If the key is null, a non-null value 
-     * representing a null key is returned.
-     * @param key The key to be set into the collection; it can be null
-     * @return key if it is non-null, or a constant representing a null key if key was null.
-     */
-    private Object keyIntoMap(Object key) {
-        if (key == null) {
-            return NULL_KEY_INDICATOR;
-        } else {
-            return key;
+     * Lock the collection for update.  NOTE: This should be called inside a try block and the 
+     * unlock method should be called inside a finally block to ensure the lock is always
+     * released!  If the collection is locked, methods that directly update the table will 
+     * block until released by the unlock.
+     * @see #unlockForUpdate()
+     */
+    void lockForUpdate() {
+        synchronized(updateLock) {
+            updateLock.setLock(true);
         }
     }
 
     /**
-     * Returns a key that was retrieved from the collection.  If the key the constant representing
-     * a previously put null key, then null will be returned.  Otherwise, the key is returned.
-     * @param key The key retreived from the collection
-     * @return null if the key represents a previously put key that was null, otherwise the 
-     * value of key is returned.
+     * Unock the collection for update.  NOTE: This should be called inside a finally block and the 
+     * lock method should be called inside a try block to ensure the lock is always
+     * released!  If the collection is locked, methods that directly update the table will 
+     * block until released by this unlock.
+     * @see #lockForUpdate()
      */
-    private Object keyFromMap(Object key) {
-        if (key == NULL_KEY_INDICATOR) {
-            return null;
-        } else {
-            return key;
-        }
-    }
-    
-    /**
-     * Returns a value that can be set into the collection.  If the valuse is null, a non-null value 
-     * representing a null value is returned.
-     * @param value The value to be set into the collection; it can be null
-     * @return value if it is non-null, or a constant representing a null value if value was null.
-     */
-    private Object valueIntoMap(Object value) {
-        if (value == null) {
-            return NULL_VALUE_INDICATOR;
-        } else {
-            return value;
+    void unlockForUpdate() {
+        synchronized(updateLock) {
+            updateLock.setLock(false);
+            updateLock.notifyAll();
         }
     }
 
     /**
-     * Returns a value that was retrieved from the collection.  If the value the constant representing
-     * a previously put null value, then null will be returned.  Otherwise, the value is returned.
-     * @param value The value retreived from the collection
-     * @return null if the value represents a previously put value that was null, otherwise the 
-     * value of value is returned.
+     * Check if the update lock is currently held.  Optionally block until the lock is released.
+     * @param wait If true, will block until the lock is released
+     * @return true if the collection is currently locked for update, false if it is not.
      */
-    private Object valueFromMap(Object value) {
-        if (value == NULL_VALUE_INDICATOR) {
-            return null;
-        } else {
-            return value;
+    boolean checkUpdateLock(boolean wait) {
+        boolean isLocked = false;
+        synchronized(updateLock) {
+            if (wait) {
+                while (updateLock.isLocked()) {
+                    try {
+                        updateLock.wait();
+                    } catch (InterruptedException e) {
+                        // Ignore the interrupt; recheck the lock and wait if appropriate.
+                    }
+                }
+            }
+            isLocked = updateLock.isLocked();
         }
+
+        return isLocked;
     }
-    
-    /**
-     * An Entry to be returned in a Set for the elements in the collection.  Note that both the key
-     * and the value may be null.
-     * 
-     * @param <K> Key type
-     * @param <V> Value type
-     */
-    class SetEntry<K, V> implements Map.Entry<K, V> {
-        private K theKey = null;
-        private V theValue = null;
-        
-        SetEntry(K key, V value) {
-            this.theKey = key;
-            this.theValue = value;
-        }
-        
-        public K getKey() {
-            return this.theKey;
-        }
 
-        public V getValue() {
-            return this.theValue;
-        }
+    class UpdateLock {
+        private boolean isLocked = false;
 
-        public V setValue(V value) {
-            this.theValue = value;
-            return this.theValue;
+        UpdateLock(boolean isLocked) {
+            this.isLocked = isLocked;
         }
-        
-    }
-    
-    /**
-     * A set of Keys returned from the collection.  Note that a key may be null.
-     * 
-     * @param <K> Key type
-     */
-    class SetKey<K> extends AbstractSet<K> {
-        Set<K> set = null;
-        SetKey(Set collectionKeySet) {
-            set = new HashSet<K>();
-            Iterator collectionIterator = collectionKeySet.iterator();
-            while (collectionIterator.hasNext()) {
-                Object useKey = collectionIterator.next();
-                set.add((K) keyFromMap(useKey));
-            }
-        }
-        @Override
-        public Iterator<K> iterator() {
-            return set.iterator();
+
+        void setLock(boolean lockValue) {
+            this.isLocked = lockValue;
         }
 
-        @Override
-        public int size() {
-            return set.size();
+        boolean isLocked() {
+            return isLocked;
         }
     }
 }

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java Thu Oct  9 11:00:33 2008
@@ -3295,7 +3295,7 @@
             log.trace(getLogIDString() + 
                       ": readExternal(): About to read properties, marker is: " + marker);
         }
-        properties = in.readMap(new ConcurrentHashMapNullSemantics());
+        properties = in.readMap(new HashMapUpdateLockable());
 
 
         //---------------------------------------------------------

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java Thu Oct  9 11:00:33 2008
@@ -572,7 +572,7 @@
         // properties
         //---------------------------------------------------------
         in.readUTF(); // read marker
-        properties = in.readMap(new ConcurrentHashMapNullSemantics());
+        properties = in.readMap(new HashMapUpdateLockable());
 
         //---------------------------------------------------------
         // axis operation meta data

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java Thu Oct  9 11:00:33 2008
@@ -434,7 +434,7 @@
         //---------------------------------------------------------
         // properties
         //---------------------------------------------------------
-        properties = in.readMap(new ConcurrentHashMapNullSemantics());
+        properties = in.readMap(new HashMapUpdateLockable());
 
         //---------------------------------------------------------
         // AxisService

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java Thu Oct  9 11:00:33 2008
@@ -378,7 +378,7 @@
         //---------------------------------------------------------
         // properties
         //---------------------------------------------------------
-        properties = in.readMap(new ConcurrentHashMapNullSemantics());
+        properties = in.readMap(new HashMapUpdateLockable());
 
         //---------------------------------------------------------
         // AxisServiceGroup

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java Thu Oct  9 11:00:33 2008
@@ -275,7 +275,7 @@
         //---------------------------------------------------------
         // properties
         //---------------------------------------------------------
-        properties = in.readMap(new ConcurrentHashMapNullSemantics());
+        properties = in.readMap(new HashMapUpdateLockable());
 
         //---------------------------------------------------------
         // "nested"

Modified: webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java Thu Oct  9 11:00:33 2008
@@ -41,7 +41,7 @@
         mc.setProperty("key4_nullValue", null);
         mc.setProperty("key5", "value5");
         
-        assertTrue(mc.properties instanceof ConcurrentHashMapNullSemantics);
+        assertTrue(mc.properties instanceof HashMapUpdateLockable);
         
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -63,7 +63,7 @@
             assertEquals("value3_nullKey", mcRead.getProperty(null));
             assertNull(mcRead.getProperty("key4_nullValue"));
             assertEquals("value5", mcRead.getProperty("key5"));
-            assertTrue(mcRead.properties instanceof ConcurrentHashMapNullSemantics);
+            assertTrue(mcRead.properties instanceof HashMapUpdateLockable);
             
         } catch (Exception ex) {
             ex.printStackTrace();
@@ -82,7 +82,7 @@
         ctx.setProperty("key4_nullValue", null);
         ctx.setProperty("key5", "value5");
         
-        assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+        assertTrue(ctx.properties instanceof HashMapUpdateLockable);
         
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -104,7 +104,7 @@
             assertEquals("value3_nullKey", ctxRead.getProperty(null));
             assertNull(ctxRead.getProperty("key4_nullValue"));
             assertEquals("value5", ctxRead.getProperty("key5"));
-            assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+            assertTrue(ctxRead.properties instanceof HashMapUpdateLockable);
             
         } catch (Exception ex) {
             ex.printStackTrace();
@@ -121,7 +121,7 @@
         ctx.setProperty("key4_nullValue", null);
         ctx.setProperty("key5", "value5");
         
-        assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+        assertTrue(ctx.properties instanceof HashMapUpdateLockable);
         
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -143,7 +143,7 @@
             assertEquals("value3_nullKey", ctxRead.getProperty(null));
             assertNull(ctxRead.getProperty("key4_nullValue"));
             assertEquals("value5", ctxRead.getProperty("key5"));
-            assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+            assertTrue(ctxRead.properties instanceof HashMapUpdateLockable);
             
         } catch (Exception ex) {
             ex.printStackTrace();
@@ -160,7 +160,7 @@
         ctx.setProperty("key4_nullValue", null);
         ctx.setProperty("key5", "value5");
         
-        assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+        assertTrue(ctx.properties instanceof HashMapUpdateLockable);
         
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -182,7 +182,7 @@
             assertEquals("value3_nullKey", ctxRead.getProperty(null));
             assertNull(ctxRead.getProperty("key4_nullValue"));
             assertEquals("value5", ctxRead.getProperty("key5"));
-            assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+            assertTrue(ctxRead.properties instanceof HashMapUpdateLockable);
             
         } catch (Exception ex) {
             ex.printStackTrace();
@@ -199,7 +199,7 @@
         ctx.setProperty("key4_nullValue", null);
         ctx.setProperty("key5", "value5");
         
-        assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+        assertTrue(ctx.properties instanceof HashMapUpdateLockable);
         
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -221,7 +221,7 @@
             assertEquals("value3_nullKey", ctxRead.getProperty(null));
             assertNull(ctxRead.getProperty("key4_nullValue"));
             assertEquals("value5", ctxRead.getProperty("key5"));
-            assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+            assertTrue(ctxRead.properties instanceof HashMapUpdateLockable);
             
         } catch (Exception ex) {
             ex.printStackTrace();

Modified: webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java?rev=703219&r1=703218&r2=703219&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java Thu Oct  9 11:00:33 2008
@@ -21,289 +21,423 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 
 import junit.framework.TestCase;
 
 /**
- * Test the setting of properties on an AbstractContext instance.  Originally the properties
- * collection was a HashMap, but was changed to a Concurrent collection because multiple threads
- * could be accessing the properties at the same time for aysnc wire flows, when processing
- * responses.
- * 
- * The intent is to retain the original semantics of the HashMap which allows null keys and null 
- * values, since areas of the code seem to make use of those semantics.  Note that most of the 
- * tests below use MessageContext as the concreate implemntation of the abstract
- * AbstractContext.
+ * Test the setting of properties on an AbstractContext instance.  Note that MessageContext is used
+ * in most of the tests as the concrete implementation of AbstractContext.  Some tests also test
+ * the HashTableUpdateLockable collection used in AbstractContext.proeprties directly. 
  */
 public class ContextPropertiesTest extends TestCase {
     
     /**
-     * Test basic property setting, including using null keys and values.
+     * Test to ensure using an Iterator from getPropertyNames() after the properties have been 
+     * updated does not cause a ConcurrentModificationException.  The CME does not happen because
+     * the Iterator is over a copy of the key names, not the actual collection (which would cause
+     * a CME in this test).
      */
-    public void testProperties() {
+    public void testPropertyNamesForCME() {
         MessageContext mc = new MessageContext();
 
-        // Test getting a non-existent key returns a null value
-        assertNull(mc.getProperty("NonexistenKey"));
-        
-        // Test setting a null property value;
-        mc.setProperty("testProperty1", null);
-        assertNull(mc.getProperty("testProperty1"));
-        
-        // Test setting a null property key
-        mc.setProperty(null, "value");
-        Object value = mc.getProperty(null);
-        assertEquals("value", (String) value);
-        
-        // Test setting a null key and value
-        mc.setProperty(null, null);
-        assertNull(mc.getProperty(null));
-
-        // Test setting a null value after a valid value; the next get should return null
-        String property2 = "testProperty2";
-        String testValue2 = "value2";
-        mc.setProperty(property2, testValue2);
-        Object value2 = mc.getProperty(property2);
-        assertEquals(testValue2, value2);
-        mc.setProperty(property2, null);
-        assertNull(mc.getProperty(property2));
-        
+        mc.setProperty("key1", "value1");
+        mc.setProperty("key2", "value2");
+        mc.setProperty(null, "value3_nullkey");
+        mc.setProperty("key4_nullvalue", null);
+
+        // Get an iterator over the key names, then add a new element.
+        // This simulates two threads accessing the context.  This should not cause
+        // a ConcurrentModificationException (it will if a live iterator over the collection
+        // key set is returned by getPropertyNames())
+        Iterator propNamesIterator = mc.getPropertyNames();
+        mc.setProperty("newKey", "newValue");
+        while (propNamesIterator.hasNext()) {
+            // Verify 
+            // (1) that calling next() on the iterator does not cause a CME, 
+            // (2) that the keys are as expected and 
+            // (3) those keys can be used to access the entries in the collection.
+            String checkKey = (String) propNamesIterator.next();
+            Iterator concurrentIterator = mc.getPropertyNames();
+            while (concurrentIterator.hasNext()) {
+                String concurrentKey = (String) concurrentIterator.next();
+            }
+        }
     }
     
     /**
-     * Test setting the properties based on an input Map.  NOTE that MessageContext has logic
-     * in setProperties(Map) that always performs a copy by setting a COPY_PROPERTIES flag.  
-     * There there is an additional test below that uses a mock object implemntation of
-     * AbstractContext to make sure the collection remains a concurrent one.
-     * @see #testPropertiesAssignment_MockAbstractContext()  
+     * Test that using an iterator of propertyNames does not cause a CME.  This is degenerate
+     * test of the test for CME that updates the collection before using the iterator.
+     * @see #testPropertyNamesForCME()
      */
-    public void testPropertiesAssignment() {
+    public void testPropertyNamesForCME_NoUpdate() {
         MessageContext mc = new MessageContext();
 
-        Map map = new HashMap();
-        map.put("keyFromMap1", "valueFromMap1");
-        map.put("keyFromMap2", "valueFromMap2");
-        map.put("keyFromMap3", null);
-        map.put(null, "valueFromMapNullKey");
-        
-        
-        mc.setProperties(map);
-        assertEquals("valueFromMap1", mc.getProperty("keyFromMap1"));
-        assertEquals("valueFromMap2", mc.getProperty("keyFromMap2"));
-        assertEquals(null, mc.getProperty("keyFromMap3"));
-        assertEquals("valueFromMapNullKey", mc.getProperty(null));
-    }
+        mc.setProperty("key1", "value1");
+        mc.setProperty("key2", "value2");
+        mc.setProperty(null, "value3_nullkey");
+        mc.setProperty("key4_nullvalue", null);
 
+        Iterator propNamesIterator = mc.getPropertyNames();
+        while (propNamesIterator.hasNext()) {
+            Iterator concurrentIterator = mc.getPropertyNames();
+            // Verify 
+            // (1) that calling next() on the iterator does not cause a CME, 
+            // (2) that the keys are as expected and 
+            // (3) those keys can be used to access the entries in the collection.
+            String checkKey = (String) propNamesIterator.next();
+        }
+    }
+    
     /**
-     * Test setting the properties based on an input Map.  Make sure that the resulting collection
-     * on the AbstractContext is a Concurrent collection.
-     * @see #testPropertiesAssignment()
+     * Test some aspects of the Concurrent collection directly, such as creating with a Map. 
      */
-    public void testPropertiesAssignment_MockAbstractContext() {
-        MockAbstractContext mc = new MockAbstractContext();
-        assertTrue(mc.getProperties() instanceof ConcurrentHashMapNullSemantics);
+    public void testHashMapUpdateLockable() {
+        
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        testMap.put("key", "value");
+        testMap.put(1, 2);
         
         Map map = new HashMap();
-        map.put("keyFromMap1", "valueFromMap1");
-        map.put("keyFromMap2", "valueFromMap2");
-        map.put("keyFromMap3", null);
-        map.put(null, "valueFromMapNullKey");
+        map.put("k1", "v1");
+        map.put("k2", "v2");
+        map.put(null, "v3NullKey");
+        map.put("k3_null", null);
+        HashMapUpdateLockable testCtorMap = new HashMapUpdateLockable(map);
+        assertEquals("v1", testCtorMap.get("k1"));
+        assertEquals("v2", testCtorMap.get("k2"));
+        assertEquals("v3NullKey", testCtorMap.get(null));
+        assertNull(testCtorMap.get("k3_null"));
         
-        mc.setProperties(map);
-        assertEquals("valueFromMap1", mc.getProperty("keyFromMap1"));
-        assertEquals("valueFromMap2", mc.getProperty("keyFromMap2"));
-        assertEquals(null, mc.getProperty("keyFromMap3"));
-        assertEquals("valueFromMapNullKey", mc.getProperty(null));
-        assertTrue(mc.getProperties() instanceof ConcurrentHashMapNullSemantics);
+        // put returns the previous value if there was one, or null
+        assertEquals("v1", testCtorMap.put("k1", "newK1Value"));
+        assertNull(testCtorMap.put("k3_null", "newK3Value"));
+        assertNull(testCtorMap.put("noSuchKey-put", "value6"));
         
+        // remove returns the value if there was an entry with that key, or null
+        assertNull(testCtorMap.remove("noSuchKey-remove"));
+        testCtorMap.put("key7", null);
+        assertNull(testCtorMap.remove("key7"));
+
     }
     
+    // =============================================================================================
+    // The following tests are multithreaded tests to test the locking, blocking, and releasing
+    // of threads by the HashTableUpdateLockable collection.
+    // =============================================================================================
+
     /**
-     * Test removing elements from the collection.
+     * Methods on HashTableUpdateLockable to be tested.  The update methods are tested to
+     * verify they block while the updateLock is held.  Some non-update methods are tested to
+     * verify they do not block if the updateLock is held. 
      */
-    public void testPropertiesRemove() {
-        MessageContext mc = new MessageContext();
-        // Remove null key not previously added, make sure it is gone
-        mc.removeProperty(null);
-        
-        // Remove null key previously added
-        mc.setProperty(null, "ValueForNullKey");
-        assertEquals("ValueForNullKey", mc.getProperty(null));
-        mc.removeProperty(null);
-        assertNull(mc.getProperty(null));
-
-        // Remove non-existent key
-        assertNull(mc.getProperty("NonexistentKey"));
-        mc.removeProperty("NonexistentKey");
-        assertNull(mc.getProperty("NonexistentKey"));
-        
-        // Remove non-null key, non-null value make sure it is gone
-        mc.setProperty("nonNullKey1", "nonNullValue1");
-        assertEquals("nonNullValue1", mc.getProperty("nonNullKey1"));
-        mc.removeProperty("nonNullKey1");
-        assertNull(mc.getProperty("nonNullKey1"));
-        
-        // Remove non-null key, null value & make sure it is gone
-        mc.setProperty("nonNullKey2", null);
-        assertEquals(null, mc.getProperty("nonNullKey2"));
-        mc.removeProperty("nonNullKey2");
-        assertNull(mc.getProperty("nonNullKey2"));
-        
-    }
+    private enum MethodToCheck {checkLock_wait, checkLock_nowait, put, putAll, remove, get};
     
-    public void testCollectionEntrySet() {
-        MockAbstractContext mc = new MockAbstractContext();
-        mc.setProperty("key1", "value1");
-        mc.setProperty("key2", "value2");
-        mc.setProperty("key3", null);
-        mc.setProperty(null, "value4-nullkey");
-        mc.setProperty("key5", "value5");
-        
-        Map checkProperties = mc.getProperties(); 
-        Set entrySet = checkProperties.entrySet();
-        Iterator entryIterator = entrySet.iterator();
-        int correctEntries = 0;
-        while (entryIterator.hasNext()) {
-            Map.Entry entry = (Map.Entry) entryIterator.next();
-            // The collection uses Object instances to represent nulls in the collection.  If 
-            // the conversion back to nulls hasn't occured, that will cause a CastClassException
-            // when it is casted to a (String).
-            try {
-                String checkKey = (String) entry.getKey();
-                String checkValue = (String) entry.getValue();
-                if ("key1".equals(checkKey) && "value1".equals(checkValue)) {
-                    correctEntries++;
-                } else if ("key2".equals(checkKey) && "value2".equals(checkValue)) {
-                    correctEntries++;
-                }  else if ("key3".equals(checkKey) && (checkValue == null)) {
-                    correctEntries++;
-                } else if ((checkKey == null) && "value4-nullkey".equals(checkValue)) {
-                    correctEntries++;
-                }  else if ("key5".equals(checkKey) && "value5".equals(checkValue)) {
-                    correctEntries++;
-                } else {
-                    fail("Invalid entry: key: " + checkKey + ", value: " + checkValue);
-                }
-            } catch (ClassCastException e) {
-                e.printStackTrace();
-                fail("Caught ClassCastException " + e.toString());
-            }
-        }
-        assertEquals(5, correctEntries);
+    /**
+     * Test HashTableUpdateLockable.remove method which overrides the HashTable method.
+     * Tests the HashTableUpdateLockable collection in a multithreaded environment to ensure that
+     * a thread trying to update the collection will block as long as another thread has the
+     * collection locked for update.  Note that UpdateRunnable performs the actual test of the
+     * method.
+     * @see UpdateRunnable#run()
+     * 
+     */
+    public void testMultithreadUpdateLock_remove() {
+        MultithreadUpdateLockMonitor testMonitor = new MultithreadUpdateLockMonitor();
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        testMap.put("testKey1", "value1");
+        testMap.put("removeKey", "value2");
+        startupTestThreads(testMonitor, testMap, MethodToCheck.remove);
+
+        assertNoThreadErrors(testMonitor);            
+
+        // Make sure the update thread blocked, i.e. the lockThread was released before the update thread.
+        assertTrue(testMonitor.lockThreadReleaseTime <= testMonitor.updateThreadReleaseTime);
     }
 
-    public void testCollectionKeySet() {
-        MockAbstractContext mc = new MockAbstractContext();
-        mc.setProperty("key1", "value1");
-        mc.setProperty("key2", "value2");
-        mc.setProperty("key3", null);
-        mc.setProperty(null, "value4-nullkey");
-        mc.setProperty("key5", "value5");
-        
-        Map checkProperties = mc.getProperties(); 
-        Set keySet = checkProperties.keySet();
-        Iterator keyIterator = keySet.iterator();
-        int correctEntries = 0;
-        while (keyIterator.hasNext()) {
-            // The collection uses Object instances to represent nulls in the collection.  If 
-            // the conversion back to nulls hasn't occured, that will cause a CastClassException
-            // when it is casted to a (String).
-            try {
-                String checkKey = (String) keyIterator.next();
-                if ("key1".equals(checkKey)) {
-                    correctEntries++;
-                } else if ("key2".equals(checkKey)) {
-                    correctEntries++;
-                }  else if ("key3".equals(checkKey)) {
-                    correctEntries++;
-                } else if ((checkKey == null)) {
-                    correctEntries++;
-                }  else if ("key5".equals(checkKey)) {
-                    correctEntries++;
-                } else {
-                    fail("Invalid entry: key: " + checkKey);
-                }
-            } catch (ClassCastException e) {
-                fail("Caught ClassCastException " + e.toString());
-            }
-        }
-        assertEquals(5, correctEntries);
+    /**
+     * Test HashTableUpdateLockable.putAll method which overrides the HashTable method.
+     * Tests the HashTableUpdateLockable collection in a multithreaded environment to ensure that
+     * a thread trying to update the collection will block as long as another thread has the
+     * collection locked for update.  Note that UpdateRunnable performs the actual test of the
+     * method.
+     * @see UpdateRunnable#run()
+     */
+    public void testMultithreadUpdateLock_putAll() {
+        MultithreadUpdateLockMonitor testMonitor = new MultithreadUpdateLockMonitor();
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        startupTestThreads(testMonitor, testMap, MethodToCheck.putAll);
+
+        assertNoThreadErrors(testMonitor);            
+
+        // Make sure the update thread blocked, i.e. the lockThread was released before the update thread.
+        assertTrue(testMonitor.lockThreadReleaseTime <= testMonitor.updateThreadReleaseTime);
     }
-    
-    public void testCollectionContainsKey() {
-        MockAbstractContext mc = new MockAbstractContext();
-        mc.setProperty("key1", "value1");
-        mc.setProperty("key2", "value2");
-        mc.setProperty("key3", null);
-        mc.setProperty(null, "value4-nullkey");
-        mc.setProperty("key5", "value5");
-        
-        Map checkCollection = mc.getProperties();
 
-        assertTrue(checkCollection.containsKey("key1"));
-        assertTrue(checkCollection.containsKey(null));
-        assertFalse(checkCollection.containsKey("notHere"));
+    /**
+     * Test HashTableUpdateLockable.put method which overrides the HashTable method.
+     * Tests the HashTableUpdateLockable collection in a multithreaded environment to ensure that
+     * a thread trying to update the collection will block as long as another thread has the
+     * collection locked for update.  Note that UpdateRunnable performs the actual test of the
+     * method.
+     * @see UpdateRunnable#run()
+     */
+    public void testMultithreadUpdateLock_put() {
+        MultithreadUpdateLockMonitor testMonitor = new MultithreadUpdateLockMonitor();
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        startupTestThreads(testMonitor, testMap, MethodToCheck.put);
+
+        assertNoThreadErrors(testMonitor);            
+
+        // Make sure the update thread blocked, i.e. the lockThread was released before the update thread.
+        assertTrue(testMonitor.lockThreadReleaseTime <= testMonitor.updateThreadReleaseTime);
     }
 
     /**
-     * Test some aspects of the Concurrent collection directly, such as creating with and without
-     * generics, and creating with a Map. 
+     * Test HashTableUpdateLockable.put method which overrides the HashTable method.
+     * Tests the HashTableUpdateLockable collection in a multithreaded environment to ensure that
+     * a thread trying to update the collection does not block as long as another thread has the
+     * collection locked for update.  Note that UpdateRunnable performs the actual test of the
+     * method.
+     * @see UpdateRunnable#run()
      */
-    public void testConcurrentHashMapNullSemantics() {
-        
-        ConcurrentHashMapNullSemantics noGenerics = new ConcurrentHashMapNullSemantics();
-        noGenerics.put("key", "value");
-        noGenerics.put(1, 2);
-        
-        ConcurrentHashMapNullSemantics<String, String> stringGenerics = new ConcurrentHashMapNullSemantics<String, String>();
-        stringGenerics.put("key", "value");
-        
-        ConcurrentHashMapNullSemantics<Integer, Integer> integerGenerics = new ConcurrentHashMapNullSemantics<Integer, Integer>();
-        integerGenerics.put(new Integer(1), new Integer(2));
-        
-        Map<String, String> map = new HashMap<String, String>();
-        map.put("k1", "v1");
-        map.put("k2", "v2");
-        map.put(null, "v3NullKey");
-        map.put("k3_null", null);
-        ConcurrentHashMapNullSemantics<String, String> useMapGenerics = new ConcurrentHashMapNullSemantics<String, String>(map);
-        assertEquals("v1", useMapGenerics.get("k1"));
-        assertEquals("v2", useMapGenerics.get("k2"));
-        assertEquals("v3NullKey", useMapGenerics.get(null));
-        assertNull(useMapGenerics.get("k3_null"));
-        
-        // put returns the previous value if there was one, or null
-        assertEquals("v1", useMapGenerics.put("k1", "newK1Value"));
-        assertNull(useMapGenerics.put("k3_null", "newK3Value"));
-        assertNull(useMapGenerics.put("noSuchKey-put", "value6"));
+    public void testMultithreadUpdateLock_get() {
+        MultithreadUpdateLockMonitor testMonitor = new MultithreadUpdateLockMonitor();
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        testMap.put("key1", "value1");
+        testMap.put("key2", "value2");
+        testMap.put("getKey", "value3");
+        startupTestThreads(testMonitor, testMap, MethodToCheck.get);
+
+        assertNoThreadErrors(testMonitor);
+
+        // The update thread shouldn't block since we said to not wait.
+        assertTrue(testMonitor.updateThreadReleaseTime <= testMonitor.lockThreadReleaseTime);
+    }
+
+    /**
+     * Test HashTableUpdateLockable.checkUpdateLock method.
+     * Tests the HashTableUpdateLockable collection in a multithreaded environment to ensure that
+     * a thread trying to update the collection will block as long as another thread has the
+     * collection locked for update.  Note that UpdateRunnable performs the actual test of the
+     * method.
+     * @see UpdateRunnable#run()
+     */
+    public void testMultithreadUpdateLock_checkLock_wait() {
+        MultithreadUpdateLockMonitor testMonitor = new MultithreadUpdateLockMonitor();
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        startupTestThreads(testMonitor, testMap, MethodToCheck.checkLock_wait);
+
+        assertNoThreadErrors(testMonitor);            
+
+        // Make sure the update thread blocked, i.e. the lockThread was released before the update thread.
+        assertTrue(testMonitor.lockThreadReleaseTime <= testMonitor.updateThreadReleaseTime);
+        // Make sure the return value indicates the table was not locked.
+        assertFalse(((Boolean) testMonitor.methodToTestReturnValue).booleanValue());
+    }
+
+    /**
+     * Test HashTableUpdateLockable.checkUpdateLock method.
+     * Tests the HashTableUpdateLockable collection in a multithreaded environment to ensure that
+     * a thread trying to update the collection will not block as long as another thread has the
+     * collection locked for update.  Note that UpdateRunnable performs the actual test of the
+     * method.
+     * @see UpdateRunnable#run()
+     */
+    public void testMultithreadUpdateLock_checkLock_nowait() {
+        MultithreadUpdateLockMonitor testMonitor = new MultithreadUpdateLockMonitor();
+        HashMapUpdateLockable testMap = new HashMapUpdateLockable();
+        startupTestThreads(testMonitor, testMap, MethodToCheck.checkLock_nowait);
+
+        assertNoThreadErrors(testMonitor);
+
+        // The update thread shouldn't block since we said to not wait.
+        assertTrue(testMonitor.updateThreadReleaseTime <= testMonitor.lockThreadReleaseTime);
+        // Make sure the return value indicates the table was locked.
+        assertTrue(((Boolean) testMonitor.methodToTestReturnValue).booleanValue());
+    }
+    
+    // Amount of time the testcase should wait on the test threads before timing out
+    private static int THREAD_TIMEOUT = 90000;
+    // Amount of time the thread holding the update lock should sleep.  This needs to be long
+    // enough so that comparisons between the time the locking thread is released and the update
+    // thread is released can be reliably compared as an indication of whether the update thread
+    // was blocked.
+    private static int LOCK_THREAD_SLEEP = 5000;
+    
+    /**
+     * Assert there were no errors in the basic running of the threads.
+     * @param testMonitor contains information about each of the test threads.
+     */
+    private void assertNoThreadErrors(MultithreadUpdateLockMonitor testMonitor) {
+        // Make sure both threads were released (i.e. release time != 0) and that the 
+        // that there were no exceptions encountered
         
-        // remove returns the value if there was an entry with that key, or null
-        assertNull(useMapGenerics.remove("noSuchKey-remove"));
-        useMapGenerics.put("key7", null);
-        assertNull(useMapGenerics.remove("key7"));
+        assertNull(testMonitor.lockThreadException);
+        assertTrue(testMonitor.lockThreadReleaseTime != 0);
         
-        ConcurrentHashMapNullSemantics useMapNoGenerics = new ConcurrentHashMapNullSemantics(map);
-        assertEquals("v1", useMapNoGenerics.get("k1"));
-        assertEquals("v2", useMapNoGenerics.get("k2"));
-
+        assertNull(testMonitor.updateThreadException);
+        assertTrue(testMonitor.updateThreadReleaseTime != 0);
     }
 
-}
+    /**
+     * Configures the test threads with the common object to store information about each one, the 
+     * collection to be tested and the method to be tested.  Two threads will be started:
+     * (1) A thread that will lock the collection, sleep, then unlock the collection
+     * (2) A thread that will try to update the collection while it is locked.
+     * Thread (2) should block if the method to be tested is one that would update the table, 
+     * otherwise it should not block.
+     * 
+     * @param testMonitor Common object used to communicate between the test method and the two
+     * test threads
+     * @param testMap Instance of HashTableUpdateLockable to test
+     * @param methodToTest The method on HashTableUpdateLockable to be tested.
+     */
+    private void startupTestThreads(MultithreadUpdateLockMonitor testMonitor, 
+                                    HashMapUpdateLockable testMap,
+                                    MethodToCheck methodToTest) {
+        LockingRunnable lockingRunnable = new LockingRunnable();
+        lockingRunnable.testMonitor = testMonitor;
+        lockingRunnable.testMap = testMap;
+        lockingRunnable.methodToTest = methodToTest;
+        
+        UpdateRunnable updateRunnable = new UpdateRunnable();
+        updateRunnable.testMonitor = testMonitor;
+        updateRunnable.testMap = testMap;
+        updateRunnable.methodToTest = methodToTest;
+
+        Thread lockingThread = new Thread(lockingRunnable, "Locking");
+        Thread updateThread = new Thread(updateRunnable, "Updating");
+
+        // To eliminate a timing window where the locking thread starts and runs to completion
+        // before the update thread gets started, start the update thread first.  It will block
+        // waiting on the locking thread to complete.
+        updateThread.start();
+        lockingThread.start();
+        
+        // Join the threads to wait for their completion, specifying a timeout to prevent 
+        // a testcase hang if something goes wrong with the threads.
+        try {
+            lockingThread.join(THREAD_TIMEOUT);
+            updateThread.join(THREAD_TIMEOUT);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            fail("Unable to join to testing threads");
+        }
+    }
+    
+    /**
+     * Object used to communicate information between the testcase and the two test threads.
+     */
+    class MultithreadUpdateLockMonitor {
+        long lockThreadReleaseTime = 0;
+        boolean lockSetupComplete = false;
+        RuntimeException lockThreadException = null;
 
-class MockAbstractContext extends AbstractContext {
+        long updateThreadReleaseTime = 0;
+        RuntimeException updateThreadException = null;
 
-    MockAbstractContext() {
-        properties = new ConcurrentHashMapNullSemantics();
+        Object methodToTestReturnValue = null;
     }
     
-    @Override
-    public ConfigurationContext getRootContext() {
-        return null;
+    /**
+     * Abstract superclass of the two test threads.
+     */
+    abstract class UpdateLockableTestRunnable implements Runnable {
+        MultithreadUpdateLockMonitor testMonitor = null;
+        MethodToCheck methodToTest = null;
+        HashMapUpdateLockable testMap = null;
     }
     
-    public Map getProperties() {
-        return properties;
+    
+    /**
+     * Test thread that will lock the collection, sleep, then unlock the collection.
+     */
+    class LockingRunnable extends UpdateLockableTestRunnable {
+
+        public void run() {
+            try {
+                // Lock the table, then relese the "lockSetupComplete" waiters, which will
+                // release the update thread.
+                // NOTE that the lock is done inside a try block and the unlock is done in 
+                // a finaly block so that it will always be executed.
+                testMap.lockForUpdate();
+                synchronized (testMonitor) {
+                    testMonitor.lockThreadReleaseTime = 0;
+                    testMonitor.lockSetupComplete = true;
+                    testMonitor.notifyAll();
+                }
+                // Sleep for a while to verify that the update thread is being blocked while 
+                // the table is locked by this thread.  Then unlock the table.
+                try {
+                    Thread.sleep(LOCK_THREAD_SLEEP);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                    testMonitor.lockThreadException = new RuntimeException(e);
+                    throw testMonitor.lockThreadException;
+            }
+            } finally {
+                // Get the current time before releasing the lock.  On some systems, as soon as the 
+                // notify is done, the waiting thread will immediately get control.
+                testMonitor.lockThreadReleaseTime = System.currentTimeMillis();
+                testMap.unlockForUpdate();
+            }
+        }
     }
     
+    /**
+     * Thread that will run the methods to be tested on the collection while it is locked for 
+     * update by another thread.
+     */
+    class UpdateRunnable extends UpdateLockableTestRunnable  {
+        public void run() {
+            // Setup whatever we'll need to test laser on
+            Map putAllMap = new HashMap();
+            putAllMap.put("k1", "v1");
+            putAllMap.put("k2", "v2");
+            putAllMap.put("k3", "v3");
+            
+            // Wait till the locking thread is setup
+            synchronized(testMonitor) {
+                testMonitor.updateThreadReleaseTime = 0;
+                while(!testMonitor.lockSetupComplete) {
+                    try {
+                        testMonitor.wait();
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                        testMonitor.updateThreadException = new RuntimeException(e);
+                        throw testMonitor.updateThreadException;
+                    }
+                }
+            }
+
+            // Test the update method specified
+            switch(methodToTest) {
+            case checkLock_wait:
+                boolean retValWait = testMap.checkUpdateLock(true);
+                testMonitor.methodToTestReturnValue = new Boolean(retValWait);
+                break;
+            case checkLock_nowait:
+                boolean retValNoWait = testMap.checkUpdateLock(false);
+                testMonitor.methodToTestReturnValue = new Boolean(retValNoWait);
+                break;
+            case put:
+                testMap.put("newKey", "newValue");
+                break;
+            case putAll:
+                testMap.putAll(putAllMap);
+                break;
+            case remove:
+                testMap.remove("removeKey");
+                break;
+            case get:
+                testMap.get("getKey");
+                break;
+            default:
+                testMonitor.updateThreadException =
+                    new UnsupportedOperationException("method to test not recognized: " + methodToTest); 
+                throw testMonitor.updateThreadException;
+            }
+            testMonitor.updateThreadReleaseTime = System.currentTimeMillis();
+        }
+    }
 }