Reputation: 3
How to loop ArrayList
from one thread ,while adding to it from other thread?
1) I have one thread which loop over ArrayList
and perform some checks.
2) I have second thread which adds to end of ArrayList
.
I have semaphore which pause first thread when second is running, but how do I implement this:
When second thread adds something to ArrayList
, first thread will continue loop from point where it was paused without throwing java.util.ConcurrentModificationException
?
Upvotes: 0
Views: 2108
Reputation: 11132
If you're bound to use an ArrayList
you have to synchronize access to it. But that still doesn't save you from ConcurrentModificationExeptions as this doen't necessarily has something to do with concurrency.
From JavaDoc
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.
If you can ensure, only new items get appended to the list, you may just use an index and loop manually over it and not use an iterator. Once you reach the end of the queue, you start all over.
ArrayList q = new ArrayList<>();
//thread 1
Object o = ...;
synchronized(q) {
q.add(o); //append
}
//thread 2
int i = 0;
synchronized(q) {
int size = q.size();
for(; i < size; i++){
Object o = q.get(i);
//do something with o
}
if(i >= size) {
i = 0;
}
}
But doing it that way will lead you to a somewhat sequential behavior as either one or the other thread may operate at once on the list. The only "advantage" is that the threading model adds some randomness to the loop operation. So you could as well skip synchronization and concurrency and just do
q.add(o); //append
//you may add a random condition, i.e. time interval, item count random number to trigger the loop process so it don't get exectued on each add
for(Object o : q){
//do something with o
}
Of course you can use a Safe-Copy as Nicolas wrote, but that's just a shallow copy of the container structure (list) and not of it's contents. So if you do that, be sure the items are thread safe or don't modify them.
In case the second thread occasionally removes an item from the end of list, you should better use a Queue. Java provides the Deque
for that. One thread may add elements at one end while the other removes from the other. As you use it in multiple threads, and synchronizing manually sucks, you should better use a thread safe implementation and skip synchronization:
Deque q = new ConcurrentLinkedDeque<>();
//thread 1
Object o = ...;
q.addFirst(o);
//thread 2
while(!q.isEmpty()){
Object o = q.removeLast();
//do something with o
}
Upvotes: 6
Reputation: 1258
The simplest solution is to use CopyonWritearrayList it is design to precisely for this kind of stuff but note that it is slower than usual ArrayList
Upvotes: 0
Reputation: 44965
Assuming that you cannot use a Queue
like ConcurrentLinkedQueue
, here is how you can proceed:
Here I assume that your checking is fast:
Thread 1:
synchronized(list) {
for (MyClass obj : list) {
// check what you want here
}
}
Thread 2:
synchronized(list) {
list.add(obj);
}
Here I assume that your checking is slow:
Thread 1:
List<MyClass> safeCopy;
synchronized(list) {
safeCopy = new ArrayList<>(list);
}
for (MyClass obj : safeCopy) {
// check what you want here
}
Thread 2:
Same as above
If you don't modify too often your list you should use CopyOnWriteArrayList
it is thread safe already, read operations are very fast as there is no locking but write operations are slow because it rebuilds the whole list
Upvotes: 1