First off: I know there are many related posts in SO about this but one that I could find was able to help in my case.
So, what I'm doing is I got a very simple parent object that may have multiple child objects. Both objects comply to the java beans specifications (no-args constructor, setter and getter for all variables).
There may be multiple parents, which are saved in a repository class like this:
private final Map<String, Parent> parentItems = new ConcurrentHashMap<String, Parent>();
Every time a new parent is created, a repository class saves the parent list which is build from the parentItems
list like this:
public List<Parent> getParents() {
return new ArrayList<Parent>(parentItems.values());
}
The save operation is done by saving the List
into an xml file using XMLEncoder
. It looks like this:
public void saveParents(OutputStream os) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
XMLEncoder encoder = null;
try {
Thread.currentThread().setContextClassLoader(Parent.class.getClassLoader());
encoder = new XMLEncoder(os);
encoder.writeObject(getParents());
encoder.flush();
} finally {
if (encoder != null) {
encoder.close();
}
Thread.currentThread().setContextClassLoader(cl);
}
}
This all works fine. Now I want to add some children to the parent object and just add new child objects to the child List and rerun saveParents()
. Here is where my trouble begins:
If I use a ArrayList
within the parent to save the children, like this:
private ArrayList<Child> children = new ArrayList<Child>();
Everything works. The objects will be saved within the parent in the xml file. It looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_45" class="java.beans.XMLDecoder">
<object class="java.util.ArrayList">
<void method="add">
<object class="org.mypackage.Parent" id="Parent0">
<void property="children">
<void method="add">
<object class="org.mypackage.Child">
<void property="displayName">
<string>Child1</string>
</void>
<void property="id">
<string>myid1</string>
</void>
<void property="parent">
<object idref="Parent0"/>
</void>
</object>
</void>
<void method="add">
<object class="org.mypackage.Child">
<void property="displayName">
<string>Child2</string>
</void>
<void property="id">
<string>myid2</string>
</void>
<void property="parent">
<object idref="Parent0"/>
</void>
</object>
</void>
</void>
<void property="displayName">
<string>Parent1</string>
</void>
<void property="id">
<string>myid</string>
</void>
</object>
</void>
</object>
</java>
However: we know that ArrayList
is not thread-safe and I know that the parent object may be altered by multiple people. So how to fix that? Use a synchronizedList(...)
- right? Something like ...
private List<Child> children = Collections.synchronizedList(new ArrayList<Child>());
... then just quickly change the getter and setter to List
instead of ArrayList
and we should be fine. Okay that should work - but guess what: it fails :-(
Every time I add a child object (and run saveParent()
after that) I get a StackOverflowException
. If I look up the XML I see something like this:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_45" class="java.beans.XMLDecoder">
<void class="java.util.ArrayList">
<void method="add">
<object class="org.mypackage.Child" id="Child0">
<void property="displayName">
<string>Child1</string>
</void>
<void property="id">
<string>myid</string>
</void>
<void property="parent">
<object class="org.mypackage.Parent"/>
</void>
</object>
</void>
</void>
<void class="java.util.ArrayList">
<void method="add">
<object idref="Child0"/>
</void>
</void>
<void class="java.util.ArrayList">
<void method="add">
<object idref="Child0"/>
</void>
</void>
<void class="java.util.ArrayList">
<void method="add">
<object idref="Child0"/>
</void>
</void>
...... AND THIS GOES ON AND ON AND ON .......
Well, guess I know where that stack overflow came from... but why? It should be serializeable but well, look at it...
How do I get a serializable, thread-safe List
(or whatever other datatype that would fit) that doesn't blow up?
Maybe any workaround you could think about in order to make that ArrayList
(that works in this scenario) thread-safe? maybe by making the whole parent object thread-safe? (however: this may have other side-effects that's why simply getting that synchronizedList(...)
to work would be the most elegant way)
I created the following minimal complete verifiable example to replicate your problem:
The above non-synchronized collection code works, and produces your desired XML encoding. Uncommenting the
parents = Collections.synchronizedList(parents);
line, and theStackOverflowException
results. Now to fix it.The problem seems to stem from the
parents
list being serialized as if was anArrayList<>
, which it is not. It is actually a non-publicjava.util.Collections$SynchronizedRandomAccessList
. If theStackOverflowException
did not occur, your question would have been "I serialized asynchronizedList-wrapped-ArrayList
, but is deserializes as just anArrayList
, which is wrong". You want to properly encode the synchronized list. For this, you need aPersistenceDelegate
.This delegate will create instructions to deserialize the list by passing to the
Collections.synchronizedList()
method anArrayList<>
, and then adding members to that resultingList<>
object. Simply install the delegate after creating theXMLEncoder
.The
#setPersistanceDelegate(cls, delegate)
needs aClass<?>
object. Normally, we'd use something likeSynchronizedArrayList.class
, but the actual class is not a public class, so we're grabbing the actual class from the object itself. This isn't type-safe; if you changedparents
to be (say) a synchronized linked list, theSynchronizedArrayListPD
would be used to synchronize that, which is wrong. Add appropriate cautions.The resulting XML encoding:
See the XML Encoder article for more info on creating Persistence Delegates.