Reputation: 864
I'm sorry this is not a SSCCE, but I've attempted to adequately describe the Objects in this problem.
Everything nested inside is a member variable
PerformanceData (Object)
|- process (String): The name of the process this PerformanceData has data for (ie: firefox.exe)
|- metrics (Map<String, List<DataValue>>): The keys are metrics whose usage we are monitoring ("cpu", "disk", "memory")
DataValue (Object)
|- value (double): The observed value (ie: 0.43 which means 43% cpu usage)
|- time (Date) : The time the value was observed
ModelEnsemble (Thread)
|- data (List<DataValue>): The DataValues available to this ModelEnsemble
|- models (Map<String, IEnsembleModel>): maps a name to each IEnsembleModel.
||-This is used because the user can choose which IEnsembleModels to run via a myApp.properties file.
||-In the constructor, we parse this properties file for enabled IEnsembleModels
IEnsembleModel (Object)
|- window (int): The max size input should be. When input.size() > window, we remove (older elements) from the front of the queue
|- input (ArrayDeque<DataValue>): The queue of DataValues we are modelling
|- addInput (DataValue): adds a DataValue to the end of the list
|- getLastInput (DataValue): The last inserted DataValue (from addInput)
|- getLastPrediction (double): The second latest predicted outcome from the current input data (minus the last input)
|- getError (double): Percent error, how much the last prediction (getLastPrediction) differed from the last input (getLastInput)
|- getNextPrediction (double): The latest predicted outcome from the current input data (including the last input)
|- model (double): Computes the next prediction, returns getNextPrediction().
The following code throws a ConcurrentModificationException. I believe it is caused by the anonymous PropertChangeListener, as well as addInput(). Could some generous SO user look at my code and point out where I'm allowing multiple edits or race conditions?
for (final PerformanceData perfData : myPerformanceData) {
for (Map.Entry<String, List<DataValue>> entry : perfData.getMetrics().entrySet()) {
final String metric = entry.getKey();
final List<DataValue> values = entry.getValue();
ensemble = new ModelEnsemble(values);
// Pretty sure this causes the Concurrent Modification Exception
ensemble.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
IEnsembleModel model = (IEnsembleModel) evt.getNewValue();
ModelPanel.this.firePropertyChange("result", null, new ModelResult(
perfData.getProcess(),
metric,
model.getLastInput().getValue(),
model.getLastPrediction(),
model.getError()
));
}
});
ensemble.start();
}
}
int offset = 0; // array index we are currently modelling
while (!this.isInterrupted()) {
if (offset > data.size() - 1) {
// we've exhausted the input input, close the model solver
System.out.println("Input input exhausted, ending ensemble");
this.interrupt();
} else {
// get the latest input value
DataValue value = data.get(offset);
for (Map.Entry<String, IEnsembleModel> entry : models.entrySet()) {
String name = entry.getKey();
IEnsembleModel model= entry.getValue();
model.addInput(value);
model.model();
this.notifyListeners(name, model); // triggers that anonymous PropertyChangeListener in the above piece of code
}
}
offset++; // so we can model the next element of the DataValue List
}
double model() {
double sum = 0;
for (DataValue value : input) {
sum += value.getValue();
}
setNextPrediction(sum / ((double) input.size()));
return getNextPrediction();
}
void addInput(DataValue input) {
this.input.addLast(input); // add to back of queue
this.maintainWindow();
}
private void maintainWindow() {
int size = this.input.size();
while (size > window) { // by default window = 10
this.input.pop(); // error here
}
}
This happens in every thread:
Exception in thread "Thread-13" java.util.ConcurrentModificationException
at java.util.ArrayDeque$DeqIterator.next(ArrayDeque.java:632)
at ca.yorku.cirillom.ensemble.models.MovingAverageModel.maintainWindow(MovingAverageModel.java:93)
at ca.yorku.cirillom.ensemble.models.MovingAverageModel.addInput(MovingAverageModel.java:53)
at ca.yorku.cirillom.ensemble.models.ModelEnsemble.run(ModelEnsemble.java:123)
This also happens in every thread:
Exception in thread "Thread-7" java.util.NoSuchElementException
at java.util.ArrayDeque.removeFirst(ArrayDeque.java:278)
at java.util.ArrayDeque.pop(ArrayDeque.java:507)
at ca.yorku.cirillom.ensemble.models.MovingAverageModel.maintainWindow(MovingAverageModel.java:85)
at ca.yorku.cirillom.ensemble.models.MovingAverageModel.addInput(MovingAverageModel.java:49)
at ca.yorku.cirillom.ensemble.models.ModelEnsemble.run(ModelEnsemble.java:123)
I Feel like there must be a race condition somewhere because maintainWindow() checks if input.size()
is larger than window
, which is 10 by default. A NoSuchElementException
can't happen if the size()
is greater than 10
Upvotes: 1
Views: 1766
Reputation: 41168
Concurrent modification exceptions can only be thrown if:
These two lines are doing the iteration so either myPerformanceData or perfData.getMetrics are being modified before these for loops exit.
for (final PerformanceData perfData : myPerformanceData) {
for (Map.Entry<String, List<DataValue>> entry : perfData.getMetrics().entrySet()) {
or alternatively this line here:
for (Map.Entry<String, IEnsembleModel> entry : models.entrySet()) {
The full stack trace of the error should give you the location where the concurrent modification happens and let you identify the collection being modified. You then just need to modify your logic so that changes to the collections happen outside of iterating over them.
Upvotes: 3