jueves
mar292012
Java puzzle: la inexplicable ConcurrentModificationException
Al ejecutar este código, cambiar cualquier elemento de la segunda columna de la tabla (la que emplea un editor diferente del estándar) y volver a hacer clic en la primera columna se lanza una ConcurrentModificationException. Sin embargo, el código parece emplear sincronización de modo correcto para proteger la lista que lanza la excepción. ¿Cuál es el problema con este código?. Las respuestas en los comentarios, por favor :)
001.
package
examen;
002.
003.
import
java.awt.*;
004.
import
java.util.*;
005.
import
java.util.List;
006.
007.
import
javax.swing.*;
008.
import
javax.swing.event.CellEditorListener;
009.
import
javax.swing.table.*;
010.
011.
public
class
ProblemaConcurrencia
extends
JFrame {
012.
013.
public
ProblemaConcurrencia() {
014.
015.
this
.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
016.
Object[][] datos = { {
"Mary"
,
"Esquiar"
, }, {
"Lucas"
,
"Esquiar"
},
017.
{
"Kathya"
,
"Escalar"
}, {
"Marcus"
,
"Andrews"
,
018.
"Correr"
}, {
"Angela"
,
"Nadar"
}
019.
};
020.
021.
String[] columnNames = {
"Nombre"
,
"Pasatiempo"
};
022.
023.
DefaultTableModel tableModel =
new
DefaultTableModel(datos, columnNames);
024.
JTable tabla =
new
JTable(tableModel);
025.
tabla.setRowHeight(
20
);
026.
TableColumnModel tablaColumnModel = tabla.getColumnModel();
027.
028.
tablaColumnModel.getColumn(
1
).setCellEditor((
new
029.
TableCellEditorJComboboxHobbbies()));
030.
031.
JScrollPane scrollPane =
new
JScrollPane(tabla);
032.
tabla.setPreferredScrollableViewportSize(
new
Dimension(
500
,
70
));
033.
getContentPane().add(scrollPane, BorderLayout.CENTER);
034.
035.
}
036.
037.
public
static
void
main(String[] args) {
038.
Runnable r =
new
Runnable() {
039.
public
void
run() {
040.
041.
ProblemaConcurrencia frame =
new
ProblemaConcurrencia();
042.
frame.pack();
043.
frame.setVisible(
true
);
044.
}
045.
};
046.
SwingUtilities.invokeLater(r);
047.
}
048.
}
049.
050.
051.
class
TableCellEditorJComboboxHobbbies
extends
JComboBox
implements
052.
TableCellEditor {
053.
054.
private
static
String[] opciones = {
"Esquiar"
,
"Patinar"
,
"Correr"
,
"Nadar"
,
055.
"Escalar"
};
056.
private
final
List listeners =
new
ArrayList<
057.
CellEditorListener>();
058.
059.
public
TableCellEditorJComboboxHobbbies() {
060.
super
(opciones);
061.
}
062.
063.
public
Component getTableCellEditorComponent(JTable table, Object value,
064.
boolean
isSelected,
int
row,
065.
int
column) {
066.
this
.setSelectedItem(value);
067.
return
this
;
068.
}
069.
070.
public
Object getCellEditorValue() {
071.
return
this
.getSelectedItem();
072.
}
073.
074.
public
boolean
isCellEditable(EventObject anEvent) {
075.
return
true
;
076.
}
077.
078.
public
boolean
shouldSelectCell(EventObject anEvent) {
079.
return
true
;
080.
}
081.
082.
public
boolean
stopCellEditing() {
083.
synchronized
(listeners) {
084.
for
(CellEditorListener l : listeners) {
085.
l.editingStopped(
null
);
086.
}
087.
}
088.
return
true
;
089.
}
090.
091.
public
void
cancelCellEditing() {
092.
synchronized
(listeners) {
093.
for
(CellEditorListener l : listeners) {
094.
l.editingCanceled(
null
);
095.
}
096.
}
097.
}
098.
099.
public
void
addCellEditorListener(CellEditorListener l) {
100.
synchronized
(listeners) {
101.
listeners.add(l);
102.
}
103.
}
104.
105.
public
void
removeCellEditorListener(CellEditorListener l) {
106.
synchronized
(listeners) {
107.
listeners.remove(l);
108.
}
109.
}
110.
}
Reader Comments (3)
El problema esta en la implementacion del listener. Deberias utilizar CopyOnWriteArrayList en vez de ArrayList.
private final java.util.List<CellEditorListener> listeners = new CopyOnWriteArrayList<CellEditorListener>();
Al pasar a la otra celda se invoca stopCellEditing, que obtiene un candado sobre la lista y la recorre para invocar editingStopped en cada listener. Si desde ese método se invoca a removeCellEditorListener, el candado que está ahí dentro ya se tiene (porque se manejan a nivel hilo) de modo que se ejecuta de inmediato el listeners.remove y entonces en la siguiente iteración dentro de stopCellEditing se arroja la excepción.
Premio a los dos :) el problema está bien descrito por Enrique, y la solución de Mauro probablemente sea la ideal.