Hi All,

I've just joined the mailing list because I saw a conversation between Adam Chesney and the Castor development team in the archives, and thought I would add my two cents. :)

Below is a link to the last email on this thread:

http://archive.castor.codehaus.org/user/00b501c767ea%24da8a1880%240a84010a%40zultan

We use Castor in much the same way as Adam: 100's of thousands of Castor generated beans, and which get loaded at runtime depends very much on the user/runtime profile of our application. I agree with Adam that initialising the XMLClassDescriptorResolver at startup is not really a useful solution - we would have to maintain a very messy (and error prone) configuration to track individual bean classes in what is a huge application, and it would also cause unnecessarily high memory usage.

Although I can't be sure, looking at the source code for XMLClassDescriptorResolverImpl.DescriptorCache in Castor 1.1, I think changing the non-threadsafe collections to synchronized ones would probably do the trick. In any case, I'm sure making a thread-safe XMLClassDescriptorResolver implementation would not be that difficult and we will be more than happy to contribute codes and run some highly concurrent tests if the Castor developers agree that this is the best way forward.

My 2 cents!

Kind regards,

        James.

P.S. Below is a short investigation into the possible effects of using HashMap in a multi-threaded environment in a non-thread-safe way.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As Adam's colleague has done, I have isolated the HashMap race condition (in both Java 1.4 and 1.5). The problem happens with concurrent writes to the map, no reads are necessary to recreate the problem (though they also have a race condition when there are concurrent writes).

Below is the source for HashMap.put:

<code>

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

</code>

As you can see, the for loop will only complete when "e != null" - that is to say, that the last entry in the table (Entry[]) is null. The call to HashMap.addEntry near the end of the method calls the HashMap.resize if the size of the array that backs the map needs to be resized. It seems that a circular reference inside one of the bins _can_ occur and _might_ cause a race condition. See the code for HashMap.transfer below:

<code>

 void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

</code>

I am able to recreate the problem with the code below. If the HashMap is initialised to "maxSize", I have not seen the race condition occur, however, if it is initalised to 1 (as in the code below), the race condition happens relatively frequently.

<code>

/**
 * HashMapTester
 * Show a race condition HashMap
 */
public class HashMapTester{

    /**
     * @param args
     */
    public static void main(String[] args) {
       new HashMapTester().run();
    }

    public void run() {
        int max = 10;
        int maxSize = 100000;
        List<HashMapWriter> writers = new ArrayList<HashMapWriter>(max);
        Map<String,String> m = new HashMap<String,String>(1);
        for(int i = 0; i < max; i++) {
            HashMapWriter writer = new HashMapWriter(m,maxSize);
            writers.add(writer);
            writer.start();
            System.out.println("Num writers created: " + (i + 1));
        }
    }


    class HashMapWriter extends Thread{
        Random r = new Random(System.currentTimeMillis());
        Map<String, String> m = null;
        int maxSize =-1;
        @Override
        public void run() {
            while(true) {
                if(m.size() < maxSize || m.size() == 0) {
                    String something = String.valueOf(m.size());
                    m.put(something,something);
                    if(m.size() % 100 == 0) {
                        System.out.println("Map size is now: " + m.size());
                    }
                }else {
                    String something = String.valueOf(r.nextInt(m.size()));
                    m.put(something, something);
                    try {
                        Thread.sleep(r.nextInt(10) +1 );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
        public HashMapWriter(Map<String, String> m, int maxSize) {
            this.m = m;
            this.maxSize = maxSize;
        }
    }
}

</code>

---------------------------------------------------------------------
To unsubscribe from this list please visit:

   http://xircles.codehaus.org/manage_email

Reply via email to