I think you are looking for a kind of double-buffering of your list.
I have tested this with multiple producers and multiple consumers and it seems to work perfectly.
Essentially you need to hold a list which, when requested, is replaced by a new empty one. Handling threading correctly while swapping adds a little complexity. This handles multiple threads adding to the list as well as multiple threads grabbing lists for iteration.
Note that a slight change in your architecture (pulling entries one-at-a-time from the list) would mean you could use a BlockingQueue
which may be a better solution for you.
public class DoubleBufferedList<T> {
// Atomic reference so I can atomically swap it through.
// Mark = true means I am adding to it so unavailable for iteration.
private AtomicMarkableReference<List<T>> list = new AtomicMarkableReference<List<T>>(newList(), false);
// Factory method to create a new list - may be best to abstract this.
protected List<T> newList() {
return new ArrayList<T>();
}
// Get and replace the current list.
public List<T> getList() {
// Atomically grab and replace the list with an empty one.
List<T> empty = newList();
List<T> it;
// Replace an unmarked list with an empty one.
if (!list.compareAndSet(it = list.getReference(), empty, false, false)) {
// Failed to replace!
// It is probably marked as being appended to but may have been replaced by another thread.
// Return empty and come back again soon.
return Collections.EMPTY_LIST;
}
// Successfull replaced an unmarked list with an empty list!
return it;
}
// Add an entry to the list.
public void addToList(T entry) {
List<T> it;
// Spin on get and mark.
while (!list.compareAndSet(it = list.getReference(), it, false, true)) {
// Spin on mark.
}
// Successfully marked! Add my new entry.
it.add(entry);
// Unmark it. Should never fail because once marked it will not be replaced.
if (!list.attemptMark(it, false)) {
throw new IllegalMonitorStateException("it changed while we were adding to it!");
}
}
}