Положение
У меня есть визуализация с использованием JGraph . График обновляется другим потоком в том, который создает экземпляр Visualization.
Ожидаемое поведение
График должен обновляться различными рабочими потоками. Функция, которую потоки вызывают для обновления графа, - syncronized
, поэтому рабочие потоки не будут вызывать проблемы параллелизма между собой.
Фактическое поведение
Существует ( иногда ) исключение, которое выдается в потоке AWT-EventQueue при рисовании. Иногда это нулевой указатель, иногда это индекс вне границ. Вот трассировка стека:
Исключение в потоке "AWT-EventQueue-0" java.lang.NullPointerException
на com.mxgraph.shape.mxConnectorShape.translatePoint (неизвестный источник)
на com.mxgraph.shape.mxConnectorShape.paintShape (неизвестный источник)
на com.mxgraph.canvas.mxGraphics2DCanvas.drawCell (неизвестный источник)
at com.mxgraph.view.mxGraph.drawState (Неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawCell (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawChildren (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawCell (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawChildren (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawCell (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawChildren (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawCell (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawFromRootCell (неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.drawGraph (Неизвестный источник)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.paintComponent (неизвестный источник)
в javax.swing.JComponent.paint (JComponent.java:1029)
на com.mxgraph.swing.mxGraphComponent $ mxGraphControl.paint (неизвестный источник)
в javax.swing.JComponent.paintChildren (JComponent.java:866)
на javax.swing.JComponent.paint (JComponent.java:1038)
в javax.swing.JViewport.paint (JViewport.java:764)
в javax.swing.JComponent.paintToOffscreen (JComponent.java:5138)
в javax.swing.BufferStrategyPaintManager.paint (BufferStrategyPaintManager.java:302)
в javax.swing.RepaintManager.paint (RepaintManager.java:1188)
в javax.swing.JComponent._paintImmediately (JComponent.java:5086)
в javax.swing.JComponent.paintImmediately (JComponent.java:4896)
в javax.swing.RepaintManager.paintDirtyRegions (RepaintManager.java:783)
в javax.swing.RepaintManager.paintDirtyRegions (RepaintManager.java:735)
в javax.swing.RepaintManager.prePaintDirtyRegions (RepaintManager.java:677)
на javax.swing.RepaintManager.access $ 700 (RepaintManager.java:58)
в javax.swing.RepaintManager $ ProcessingRunnable.run (RepaintManager.java:1593)
в java.awt.event.InvocationEvent.dispatch (InvocationEvent.java:226)
в java.awt.EventQueue.dispatchEventImpl (EventQueue.java:647)
в java.awt.EventQueue.access $ 000 (EventQueue.java:96)
at java.awt.EventQueue $ 1.run (EventQueue.java:608)
at java.awt.EventQueue $ 1.run (EventQueue.java:606)
at java.security.AccessController.doPrivileged (собственный метод)
в java.security.AccessControlContext $ 1.doIntersectionPrivilege (AccessControlContext.java:105)
в java.awt.EventQueue.dispatchEvent (EventQueue.java:617)
в java.awt.EventDispatchThread.pumpOneEventForFilters (EventDispatchThread.java:275)
в java.awt.EventDispatchThread.pumpEventsForFilter (EventDispatchThread.java:200)
в java.awt.EventDispatchThread.pumpEventsForHierarchy (EventDispatchThread.java:190)
в java.awt.EventDispatchThread.pumpEvents (EventDispatchThread.java:185)
в java.awt.EventDispatchThread.pumpEvents (EventDispatchThread.java:177)at java.awt.EventDispatchThread.run (EventDispatchThread.java:138)
Кажется, это не оказывает негативного влияния на работу приложения. Появляется новый AWT-EventQueue, который на каком-то этапе неизбежно столкнется с той же проблемой.
Причина
Я думаю, что это должно быть вызвано обновлением графика в отдельном потоке до того, в котором создается экземпляр JFrame. Вещи, которые нужно рисовать, изменяются, пока метод рисования пытается их нарисовать.
Вопрос
Как мне обойти эту проблему? Можно ли как-то синхронизировать метод рисования с методом, который обновляет график?
Код визуализации
package ui;
import javax.swing.JFrame;
import com.mxgraph.layout.mxOrganicLayout;
import com.mxgraph.layout.mxStackLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.view.mxGraph;
import core.Container;
import core.Node;
import core.WarehouseGraph;
public class Visualisation extends JFrame{
private static final long serialVersionUID = 8356615097419123193L;
private mxGraph graph = new mxGraph();
Object parent = graph.getDefaultParent();
mxOrganicLayout graphlayout = new mxOrganicLayout(graph);
mxStackLayout containerLayout = new mxStackLayout(graph, true, 10);
public Visualisation(WarehouseGraph model){
super("Warehouse Simulator");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(800, 600);
graph.getModel().beginUpdate();
try{
for(Node node : model.getNodes().values()){
mxCell cell = (mxCell)graph.insertVertex(parent, node.getId(), node.getId(), 0, 0, 60, 30);
Object prev = null;
for(Container container : node.getContainers()){
Object newCont = graph.insertVertex(cell, container.getId(), container.getId(), 0, 0, 60, 20);
if(prev != null) graph.insertEdge(cell, null, null, prev, newCont);
prev = newCont;
}
}
for(Node node : model.getNodes().values()){
for(Node toNode : node.getDownstreamNodes()){
Object fromCell = ((mxGraphModel)graph.getModel()).getCell(node.getId());
Object toCell = ((mxGraphModel)graph.getModel()).getCell(toNode.getId());
graph.insertEdge(parent, null, null, fromCell, toCell);
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
graph.getModel().endUpdate();
}
graphlayout.execute(parent);
Object[] nodes = mxGraphModel.getChildVertices(graph.getModel(), parent);
for(Object cell : nodes){
containerLayout.execute(cell);
graph.updateCellSize(cell);
}
mxGraphComponent graphComponent = new mxGraphComponent(graph);
getContentPane().add(graphComponent);
this.setVisible(true);
}
//CALLED FROM SYNCHRONIZED FUNCTION
public void moveContainer(Container container, Node to){
graph.getModel().beginUpdate();
mxCell toCell = (mxCell)((mxGraphModel)graph.getModel()).getCell(to.getId());
mxCell containerCell = (mxCell)((mxGraphModel)graph.getModel()).getCell(container.getId());
mxCell fromCell = (mxCell)containerCell.getParent();
try{
Object[] edges = mxGraphModel.getEdges(graph.getModel(), containerCell);
graph.removeCells(edges);
containerCell.removeFromParent();
graph.addCell(containerCell, toCell);
Object[] containers = mxGraphModel.getChildVertices(graph.getModel(), toCell);
if(containers.length >= 2)
graph.insertEdge(toCell, null, null, containerCell, containers[containers.length-2]);
containerLayout.execute(toCell);
containerLayout.execute(fromCell);
}catch(Exception e){
e.printStackTrace();
}finally{
graph.getModel().endUpdate();
}
}
}
Код контроллера
package core;
import serialisation.JsonUtil;
import ui.Visualisation;
public class Controller{
private WarehouseGraph graph;
Visualisation viz;
public static void main(String[] args){
Controller.getInstance();
}
private Controller(){
graph = JsonUtil.initFromJson(); //graph has many worker threads running which can call containerMoved
viz = new Visualisation(graph);
}
private static class SingletonHolder{
private static final Controller INSTANCE = new Controller();
}
public static Controller getInstance(){
return SingletonHolder.INSTANCE;
}
public synchronized void containerMoved(Container container, Node from, Node to){
viz.moveContainer(container, to);
}
}