어딘가에는 블랙홀에서 호출 미친 & 지저분한 SwingWorker
전에 대한 JTable
& Performance
이며,
이 프로젝트는 ChristmastTree
불렀다, 쓰레기 (옛 태양 튜토리얼)에 Oracles
clean_up에
표준 자바 코드를 분실 Executor
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.table.*;
/**
* CTTable extends JTable doing the following: <ul> <li>The UI is forced to be
* CTTableUI so that a customer CellRendererPane can be installed.
* <li>getCellRenderer is overriden to return the TableCellRenderer passed into
* the constructor. <li>tableChanged is overriden to pass the call to super only
* if the cell is visible. </ul>
*/
public class CTTable extends JTable {
private static final long serialVersionUID = 1L;
private CTTableCellRenderer renderer;
public CTTable(CTTableCellRenderer renderer) {
super();
this.renderer = renderer;
renderer.setFont(getFont());
}
@Override
public void updateUI() {
super.updateUI();
//Force the UI to be an instanceof CTTableUI. This approach will not work
//if you need to support more than one look and feel in your application.
setUI(new CTTableUI());
}
@Override
public void setFont(Font font) {
super.setFont(font);
if (renderer != null) {
renderer.setFont(font);
}
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderer;
}
@Override
public void tableChanged(TableModelEvent e) {
if (e instanceof VisibleTableModelEvent && !((VisibleTableModelEvent) e).isVisible(this)) {
return;// Do nothing if this cell isn't visible.
}
super.tableChanged(e);
}
private static class CTTableUI extends BasicTableUI {
@Override
public void installUI(JComponent c) {
super.installUI(c);// Overriden to install our own CellRendererPane
c.remove(rendererPane);
rendererPane = new CTCellRendererPane();
c.add(rendererPane);
}
}
/**
* CTCellRendererPane overrides paintComponent to NOT clone the Graphics
* passed in and NOT validate the Component passed in. This will NOT work if
* the painting code of the Component clobbers the graphics (scales it,
* installs a Paint on it...) and will NOT work if the Component needs to be
* validated before being painted.
*/
private static class CTCellRendererPane extends CellRendererPane {
private static final long serialVersionUID = 1L;
private Rectangle tmpRect = new Rectangle();
@Override
public void repaint() {
// We can safely ignore this because we shouldn't be visible
}
@Override
public void repaint(int x, int y, int width, int height) {
}
@Override
public void paintComponent(Graphics g, Component c, Container p, int x,
int y, int w, int h, boolean shouldValidate) {
if (c == null) {
if (p != null) {
Color oldColor = g.getColor();
g.setColor(p.getBackground());
g.fillRect(x, y, w, h);
g.setColor(oldColor);
}
return;
}
if (c.getParent() != this) {
this.add(c);
}
c.setBounds(x, y, w, h);
// As we are only interested in using a JLabel as the renderer,
//which does nothing in validate we can override this to do nothing,
//if you need to support components that can do layout, this will
//need to be commented out, or conditionally validate.
shouldValidate = false;
if (shouldValidate) {
c.validate();
}
boolean wasDoubleBuffered = false;
JComponent jc = (c instanceof JComponent) ? (JComponent) c : null;
if (jc != null && jc.isDoubleBuffered()) {
wasDoubleBuffered = true;
jc.setDoubleBuffered(false);
}//Don't create a new Graphics, reset the clip and translate the origin.
Rectangle clip = g.getClipBounds(tmpRect);
g.clipRect(x, y, w, h);
g.translate(x, y);
c.paint(g);
g.translate(-x, -y);
g.setClip(clip.x, clip.y, clip.width, clip.height);
if (wasDoubleBuffered) {
jc.setDoubleBuffered(true);
}
c.setBounds(-w, -h, 0, 0);
}
}
}
.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
/**
* A custom TableCellRenderer that overrides a handful of methods: <ul>
* <li>isOpaque and setBackground are overridden to avoid filling the
* background, if possible. <li>firePropertyChange is overridden to do nothing.
* If you need to support HTML text in the renderer than this should NOT be
* overridden. <li>paint is overridden to forward the call directly to the UI,
* avoiding the creation of a Graphics. This will NOT work if you need the
* renderer to contain other childre or the Graphics is clobbered as part of
* painting the UI. </ul>
*/
public class CTTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private Color background;
private Color foreground;
private Color editableForeground;
private Color editableBackground;
private Border focusBorder;
public CTTableCellRenderer() {
focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
editableForeground = UIManager.getColor("Table.focusCellForeground");
editableBackground = UIManager.getColor("Table.focusCellBackground");
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
boolean negative = (value != null && ((Integer) value).intValue() < 0);
// Reset the background based on the sign of the value.
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
if (!negative) {
setBackground(null);
} else {
setBackground(Color.red);
}
}//NOTICE that we do NOT set the font here, because CTTable knows about
//us, it will set the font as appropriate.
if (hasFocus) {
setBorder(focusBorder);
if (table.isCellEditable(row, column)) {
setForeground(editableForeground);
setBackground(editableBackground);
}
} else {
setBorder(noFocusBorder);
}
setValue(value);
return this;
}
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// As long as you don't have any HTML text, this override is ok.
}
@Override// This override is only appropriate if this will never contain
// any children AND the Graphics is not clobbered during painting.
public void paint(Graphics g) {
ui.update(g, this);
}
@Override
public void setBackground(Color c) {
this.background = c;
}
@Override
public Color getBackground() {
return background;
}
@Override
public void setForeground(Color c) {
this.foreground = c;
}
@Override
public Color getForeground() {
return foreground;
}
@Override
public boolean isOpaque() {
return (background != null);
}
@Override // This is generally ok for non-Composite components (like Labels)
public void invalidate() {
}
@Override // Can be ignored, we don't exist in the containment hierarchy.
public void repaint() {
}
}
.
import javax.swing.table.*;
import java.util.*;
/**
* CTTableModel, a TableModel, models a set of Datas as the rows. The data is
* stored in a List of Lists. As the changes come in against a particular Data
* object we also contain a map from Data to row. This can obviously be made
* faster by pushing the row to the Data, but this may not be feasable in
* applications of this sort.
*/
public class CTTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
/**
* Maps from Data to an integer id giving the row of the data.
*/
private Map rowMap;
/**
* Number of columns to display.
*/
private int columns;
/**
* A List of Lists.
*/
private java.util.List rowData;
/**
* If true, batch cell updates using sharedModelEvent.
*/
private boolean batchChange;
/**
* Shared model event.
*/
private VisibleTableModelEvent sharedModelEvent;
public CTTableModel(int columns) {
this.columns = columns;
// Notice how they are not synchronized, we do NOT access this class
// from another thread, and therefore do not have to worry about
// synchronization.
rowData = new ArrayList();
rowMap = new HashMap();
}
public void addRow(Data rowID) {
int row = rowData.size();
rowMap.put(rowID, new Integer(row));
ArrayList colData = new ArrayList();
for (int counter = 0; counter < columns; counter++) {
colData.add(null);
}
rowData.add(colData);
fireTableRowsInserted(row, row);
}
/**
* Toggles batch updates. When true and model changes are notified using a
* VisibleTableModelEvent.
*
* @param batch
*/
public void setBatchUpdates(boolean batch) {
this.batchChange = batch;
if (sharedModelEvent == null) {
sharedModelEvent = new VisibleTableModelEvent(this);
}
sharedModelEvent.reset();
}
public boolean getBatchUpdates() {
return batchChange;
}
/**
* Sets the display value for a particular Data item at a particular cell.
* If notify is true listeners are notified, otherwise no listeners are
* notified.
*
* @param rowID
* @param col
* @param data
* @param notify
*/
public void set(Data rowID, int col, Object data, boolean notify) {
int row = ((Integer) rowMap.get(rowID)).intValue();
((java.util.List) rowData.get(row)).set(col, data);
if (notify) {
if (batchChange) {
sharedModelEvent.set(row, col);
fireTableChanged(sharedModelEvent);
} else {
fireTableCellUpdated(row, col);
}
}
}
@Override
public int getRowCount() {
return rowData.size();
}
@Override
public int getColumnCount() {
return columns;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return ((java.util.List) rowData.get(rowIndex)).get(columnIndex);
}
}
.
/**
* Unique ID for the data.
*/
public class Data {
/**
* This is overriden to remind developers they should have an intelligent
* equals and hashCode. You do not need to override either of them, but if
* you override one you need to override the other. Additionaly, because
* they are used extensively to map the data that has changed to the table,
* equals and hashCode MUST be fast, cache data if you need to!
*
* @param x
*/
@Override
public boolean equals(Object x) {
return (this == x);
}
/**
* This is overriden to remind developers they should have an intelligent
* equals and hashCode. You do not need to override either of them, but if
* you override one you need to override the other. Additionaly, because
* they are used extensively to map the data that has changed to the table,
* equals and hashCode MUST be fast, cache data if you need to!
*/
@Override
public int hashCode() {
return super.hashCode();
}
}
.
import java.util.ArrayList;
/**
* DataChange is used to associate a Data Object with a column identifier that
* has changed. To avoid loads of garbage per update DataChanges are cached and
* reused.
*/
public class DataChange {
private static ArrayList sharedDataChanges = new ArrayList();
private Data data;
private int col;
private int hashCode;
/**
* Obtains a DataChange for the specified Data and column.
*
* @param data
* @param col
* @return
*/
public static DataChange getDataChange(Data data, int col) {
synchronized (sharedDataChanges) {
int size = sharedDataChanges.size();
if (size > 0) {
DataChange change = (DataChange) sharedDataChanges.remove(size - 1);
change.data = data;
change.col = col;
return change;
}
}
return new DataChange(data, col);
}
/**
* Indicates the DataChange is no longer needed and can be reused.
*
* @param change
*/
public static void releaseDataChange(DataChange change) {
synchronized (sharedDataChanges) {
sharedDataChanges.add(change);
}
}
DataChange(Data data, int col) {
this.data = data;
this.col = col;
hashCode = (data.hashCode() | col);
}
public Data getData() {
return data;
}
public int getColumn() {
return col;
}
@Override
public int hashCode() {
return hashCode;
}
public boolean equals(DataChange dc) {
if (dc == this) {
return true;
}
DataChange o = (DataChange) dc;
return (o.data == data && o.col == col);
}
}
.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* The Main controller, responsible for wiring everything together. Pressing
* return in any of the fields will trigger recreation of everything.
*/
public class Main implements ActionListener {
// properties: columnCount, rowCount, updateSleepTime, eqSleepTime,
// threshold, generateSleep, generatorBatchCount
private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
private JTextField columnCount;
private JTextField rowCount;
private JTextField updateSleepTime;
private JTextField eqSleepTime;
private JTextField threshold;
private JTextField generateSleep;
private JTextField generatorBatchCount;
private JFrame frame;
static JLabel totalUpdateTime;
static JLabel notifyTime;
static JLabel paintTime;
static JLabel updateCount;
private JTable table;
private UpdateThread updateThread;
private GeneratorThread generatorThread;
private CTTableModel tableModel;
private static int NUM_COLUMNS = 40;// Initial values for the 7 properties.
private static int NUM_ROWS = 3000;
private static int UPDATE_SLEEP_TIME = 500;
private static int EQ_SLEEP_TIME = 10;
private static int UPDATE_ALL_THRESHOLD = 400000;
private static int GENERATOR_SLEEP_TIME = 40;
private static int BATCH_SIZE = 1000;
Main() {
frame = new JFrame();
frame.getContentPane().setLayout(new GridBagLayout());
columnCount = add("Columns: ", NUM_COLUMNS);
rowCount = add("Rows: ", NUM_ROWS);
updateSleepTime = add("Update Sleep: ", UPDATE_SLEEP_TIME);
eqSleepTime = add("EQ Sleep: ", EQ_SLEEP_TIME);
threshold = add("Update All Threshold: ", UPDATE_ALL_THRESHOLD);
generateSleep = add("Generator Sleep: ", GENERATOR_SLEEP_TIME);
generatorBatchCount = add("Batch Size: ", BATCH_SIZE);
table = new CTTable(new CTTableCellRenderer());
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
JScrollPane sp = new JScrollPane(table);
frame.getContentPane().add(sp, new GridBagConstraints(0, 3, 6, 1, 1, 1,
GridBagConstraints.WEST, GridBagConstraints.BOTH, EMPTY_INSETS, 0, 0));
ChangeListener changeListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
BoundedRangeModel m = (BoundedRangeModel) (e.getSource());
if (updateThread != null) {
updateThread.setUpdatesEnabled(!(m.getValueIsAdjusting()));
}
}
};
sp.getVerticalScrollBar().getModel().addChangeListener(changeListener);
sp.getHorizontalScrollBar().getModel().addChangeListener(changeListener);
totalUpdateTime = new JLabel(" ");
notifyTime = new JLabel(" ");
paintTime = new JLabel(" ");
updateCount = new JLabel(" ");
JPanel statusPanel = new JPanel(new GridBagLayout());
frame.getContentPane().add(statusPanel, new GridBagConstraints(0, 4, 6, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(totalUpdateTime, new GridBagConstraints(0, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(notifyTime, new GridBagConstraints(1, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(paintTime, new GridBagConstraints(2, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(updateCount, new GridBagConstraints(3, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
frame.setTitle("Christmas Tree Demo Application");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 1000, 800);
frame.setVisible(true);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
reset();
}
@Override
public void actionPerformed(ActionEvent ae) {
reset();
}
private JTextField add(String name, int defaultValue) {
Container parent = frame.getContentPane();
int row = parent.getComponentCount()/6;
int col = parent.getComponentCount() % 6;
parent.add(new JLabel(name), new GridBagConstraints(col, row, 1, 1, 0, 0,
GridBagConstraints.WEST, 0, EMPTY_INSETS, 0, 0));
JTextField tf = new JTextField(Integer.toString(defaultValue));
tf.addActionListener(this);
parent.add(tf, new GridBagConstraints(col + 1, row, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
return tf;
}
private void reset() {
System.out.println("Columns: " + getInt(columnCount));
System.out.println("Rows: " + getInt(rowCount));
System.out.println("Update Sleep: " + getInt(updateSleepTime));
System.out.println("EQ Sleep: " + getInt(eqSleepTime));
System.out.println("Update All Threshold: " + getInt(threshold));
System.out.println("Generator Sleep: " + getInt(generateSleep));
System.out.println("Batch Size: " + getInt(generatorBatchCount));
if (updateThread != null) {
System.out.println("interrupting!");
updateThread.interrupt();
generatorThread.interrupt();
}
int cols = getInt(columnCount);
tableModel = new CTTableModel(cols);
ArrayList<Data> data = new ArrayList<Data>();
for (int counter = getInt(rowCount) - 1; counter >= 0; counter--) {
Data dataID = new Data();
data.add(dataID);
tableModel.addRow(dataID);
for (int colCounter = 0; colCounter < cols; colCounter++) {
if (colCounter % 2 == 0) {
tableModel.set(dataID, colCounter,
new Integer(counter * 100 + colCounter), false);
} else {
tableModel.set(dataID, colCounter,
new Integer(counter * -100 + colCounter), false);
}
}
}
table.setModel(tableModel);
generatorThread = new GeneratorThread(data, getInt(generateSleep),
getInt(generatorBatchCount), getInt(columnCount));
updateThread = new UpdateThread(generatorThread, tableModel,
getInt(updateSleepTime), getInt(eqSleepTime), getInt(threshold));
generatorThread.start();
updateThread.start();
}
private int getInt(JTextField tf) {
try {
return Integer.parseInt(tf.getText());
} catch (NumberFormatException nfe) {
System.out.println("exception getting int: " + nfe);
}
return 0;
}
public static void main(String[] args) {
Main main = new Main();
}
}
은 ... 방법
import java.awt.*;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.*;
/**
* Thread responsible for publishing changes to the Model. Sleeps for a defined
* amount of time, waits for no activity in the UI and then users invokeAndWait
* to publish changes.
*/
public class UpdateThread extends Thread {
private int sleepTime;
private int eqSleepTime;
private int threshhold;
private boolean updatesEnabled;
private Runnable publishRunnable;
private Runnable emptyRunnable;
private GeneratorThread generator;
private CTTableModel model;
private Map<?, ?> lastData;
private long notifyTime;
private long paintTime;
private int updateCount;
private boolean done;
public UpdateThread(GeneratorThread generator, CTTableModel model,
int sleepTime, int eqSleepTime, int threshhold) {
super();
setPriority(Thread.MIN_PRIORITY);
this.sleepTime = sleepTime;
this.eqSleepTime = eqSleepTime;
updatesEnabled = true;
this.threshhold = threshhold;
this.generator = generator;
this.model = model;
publishRunnable = new Runnable() {
// Runnable used to publish changes to the event dispatching thread
@Override
public void run() {
publishChangesOnEventDispatchingThread();
}
};
// Empty runnable, used to wait until the event dispatching thread
// has finished processing any pending events.
emptyRunnable = new Runnable() {
@Override
public void run() {
}
};
}
@Override
public void interrupt() {
done = true;
super.interrupt();
}
@Override
public void run() {
while (!isInterrupted() && !done) {
try {
sleep(sleepTime);
publishChanges();
} catch (InterruptedException ie) {
}
}
System.out.println("UpdateThread done");
}
/**
* Publishes changes on the event dispatching thread when the system isn't
* busy. This blocks the caller until the changes have been published.
*/
private void publishChanges() {
synchronized (this) {// Wait until the user isn't scrolling
while (!updatesEnabled) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
// And wait until there are no pending events.
while (queue.peekEvent() != null) {
try {
sleep(eqSleepTime);
} catch (InterruptedException ie) {
}
}
final long start = System.currentTimeMillis();
try {
SwingUtilities.invokeAndWait(publishRunnable);
// publish the changes on the event dispatching thread
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {
}
try {
// Wait until the system has completed processing of any events we
// triggered as part of publishing changes.
SwingUtilities.invokeAndWait(emptyRunnable);
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {
}
final long end = System.currentTimeMillis();
try {// Update the display
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
Main.totalUpdateTime.setText("Total: "
+ Integer.toString((int) (end - start)));
Main.notifyTime.setText("Notify: "
+ Integer.toString((int) notifyTime));
Main.paintTime.setText("Paint: "
+ Integer.toString((int) paintTime));
Main.updateCount.setText("Updated: "
+ Integer.toString((int) updateCount));
}
});
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {
}
}
/**
* Does the actual publishing of changes.
*/
private void publishChangesOnEventDispatchingThread() {
long start = System.currentTimeMillis();
model.setBatchUpdates(true);
Map<?, ?> data = generator.getData();
boolean notify = !(data.size() > threshhold ||
(lastData != null && lastData.size() + data.size() > threshhold));
updateCount = data.size();
if (lastData != null) {
updateCount += lastData.size();
}//Reset the data for the last set of changes we did, this forces the cells to change color.
if (lastData != null) {
publishData(lastData, true, notify);
Iterator<?> dataIterator = lastData.keySet().iterator();
while (dataIterator.hasNext()) {
DataChange.releaseDataChange((DataChange) dataIterator.next());
}
lastData.clear();
}
publishData(data, false, notify);// Publish the current set of data.
model.setBatchUpdates(false);
if (!notify) {
model.fireTableDataChanged();
}
lastData = data;
long end = System.currentTimeMillis();
notifyTime = (end - start);
start = System.currentTimeMillis();
RepaintManager.currentManager(null).paintDirtyRegions();
end = System.currentTimeMillis();
paintTime = (end - start);
}
/**
* Publish the passed in set of data.
*/
private void publishData(Map<?, ?> data, boolean negate, boolean notify) {
Iterator<?> dataIterator = data.keySet().iterator();
while (dataIterator.hasNext()) {
DataChange change = (DataChange) dataIterator.next();
Object value = data.get(change);
if (negate) {
value = new Integer(((Integer) value).intValue() * -1);
}
model.set(change.getData(), change.getColumn(), value, notify);
}
}
/**
* If enable is true, we are allowed to publish changes, otherwise we
* aren't.
*
* @param enable
*/
public void setUpdatesEnabled(boolean enable) {
synchronized (this) {
updatesEnabled = enable;
if (updatesEnabled) {
notify();
}
}
}
public boolean getUpdatesEnabled() {
return updatesEnabled;
}
}
의
* "내가 참조한 코드는 독점적이며 멀티 캐스트 서버가 필요하므로 SSCCE 코드를 게시 할 수 없습니다."* SSCCE에는 멀티 캐스트 서버 또는 독점 요소가 필요하지 않습니다. SSCCE에 임의의 간격으로 데이터를 생성하여 문제를 재현 할 수있는 클래스를 만드는 것이 가능할 것으로 생각됩니다. –
[예] (http://stackoverflow.com/a/4637725)에 대한 ['SwingWorker'] (http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html)가 필요할 것입니다./230513). – trashgod
@trashgod는 단일 루프에 동의했지만 무한 루프에 대해서는 동의하지 않습니다 .--) SwingWorker는 한 번만 실행되도록 지정되었습니다. 이전 PC에서 발견 된 내용을 확인하십시오. – mKorbel