Reputation: 1938
I'm trying to figure out how to synchronize read/write access to a synchronized list from a different class.
A small example: I have a synchronized list in one class ListProvider
and I access it in a different class Filter
. As the name suggests, this class performs some filtering based on a (in)validation check isInvalid
.
The filter
method first gets the list reference, then collects the entries to remove in a temporary list to not run into concurrent modification issues, and finally removes the entries from the list:
public class Filter {
ListProvider listProvider;
...
public void filter() {
List<String> listProviderList = listProvider.getList();
List<String> entriesToRemove = new ArrayList<>();
// collect
for (String entry : listProviderList)
if (isInvalid(entry)) {
entriesToRemove.add(entry);
}
}
// remove
for (String entry : entriesToRemove) {
listProviderList.remove(entry);
}
}
}
My question: How can I make sure that no other thread modifies the list while filter
does its reading and writing?
If it were Filter
's own list, I'd just do:
synchronized(myList) {
// collect
// remove
}
but in this case I'm not sure what to use as a monitor.
Upvotes: 1
Views: 244
Reputation: 10161
Do not iterate over original list, but create a copy of it to find invalid elements. When you are done with filtering you can remove invalid elements from original list safely:
public class Filter {
ListProvider listProvider;
...
public void filter() {
List<String> listProviderCopy = new ArrayList<>(listProvider.getList());
List<String> entriesToRemove = new ArrayList<>();
// collect
for (String entry : listProviderCopy)
if (isInvalid(entry)) {
entriesToRemove.add(entry);
}
}
listProvider.getList().removeAll(entriesToRemove);
}
}
Upvotes: 1
Reputation: 37855
Here it seems like you should use a lock. A lock is like synchronized but it's a bit more flexible. It doesn't require a surrounding block and it has some extended features. There are also some different kinds of locks. ReentrantLock is much like synchronized.
public class ListProvider<E> {
private final List<E> theList = new ArrayList<E>();
private final ReentrantLock listLock = new ReentrantLock();
public final List<E> lockList() {
listLock.lock();
return theList;
}
public final void unlockList() {
listLock.unlock();
}
}
/* somewhere else */ {
List<E> theList = listProvider.lockList();
/*
* perform
* multiple
* operations
*
*/
listProvider.unlockList();
}
The main differences between this and synchronized are:
There is a lock called ReentrantReadWriteLock which you might find useful because multiple threads can read simultaneously. ReadWriteLock explains how it works.
Upvotes: 2
Reputation: 116908
but in this case I'm not sure what to use as a monitor.
To create a monitor for a specific task, it is a good pattern to use a private final Object
:
private final Object listUpdateLock = new Object();
...
synchronized(listUpdateLock) {
...
}
It's important to make sure that ListProvider
is private
and that all accesses to the list are done within a synchronized
block -- even if only reading from it.
In this case, you are updating the list, you could create a temporary list and then replace it when you are done. I'm not sure you can do that with ListProvider
however. Then you could just make the list volatile
.
Upvotes: 2
Reputation: 30875
You may want to use SynchronizedList
List<String> list = new ArrayList<>();
List<String> synch = Collections.synchronizedList(list);
Upvotes: 0