【发布时间】:2013-12-26 06:10:33
【问题描述】:
我在应用程序启动时使用JDialog 来多次显示消息。有时对话框及其控件是不可见的,但可以单击。
JDialog 仅实例化一次,并在每次显示消息时设置为可见“真”,然后设置为可见“假”,直到显示下一条消息。
为了排除多线程相关问题,当线程创建消息并显示对话框时,我总是使用SwingUtilities.invokeLater(...) 进行ui 调用。
因为它是一个庞大的项目,而且我的问题与任何特定代码无关,所以我不发布代码而是描述问题。这些问题似乎无法重现,但有时会发生,因此尽管在 EDT 上运行了每个相关调用,但它可能是一个线程问题。
我做错了什么?
public class MessageHandler {
private volatile static MessageHandler messageHandler = null;
private List<Message>messages = null;
private volatile WeakReference<MessagesPanelControl> view = null;
private final Object viewSynchronizationObject = new Object();
private MessageHandler() {
messages = new ArrayList<Message>();
}
public static MessageHandler getInstance() {
MessageHandler result = messageHandler;
if (result == null) {
synchronized (MessageHandler.class) {
result = messageHandler;
if (result == null)
messageHandler = result = new MessageHandler();
}
}
return result;
}
public void registerView(MessagesPanelControl view) {
this.view = new WeakReference<MessagesPanelControl>(view);
}
public void addMessage(final Message message) {
synchronized (viewSynchronizationObject) {
messages.add(message);
Collections.sort(messages);
updateView();
}
}
private void updateView() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
synchronized (viewSynchronizationObject) {
if (view == null) {
return;
}
MessagesPanelControl mpc = view.get();
if (mpc != null) {
mpc.updateView();
}
}
}
});
}
}
在 MainFrame 类中,我在启动时进行了一次初始化:
MessagesPanelControl mp = new MessagesPanelControl();
MessageHandler.getInstance().registerView(mp);
LockPane messageBasicPane = new LockPane(this, mp);
然后在不同的线程中调用它以通过MessageHandler Singleton 显示消息:
MessageHandler.getInstance().addMessage(Message.getSimpleMessage("Error", "Fatal error occured", Message.MessageIcon.ERROR));
我没有发布所有细节,但所有必要的部分都是为了理解整个问题,希望它能让它更容易理解。
MessagePanelControl (mpc) 是一个扩展 JPanel 的类。它的updateView() 方法基于MessageHandler's 消息列表创建消息控件,如按钮、标签和图标。最后,该方法向主框架发送Delegatelike 命令以显示包含MessagePanelControl 的JDialog。
总结如下:
- messageList.size()>0:为
MessageHandler中列表中的每条消息创建消息面板 - messageList.size()>0:显示带有
MessagePanelControl的JDialog -
messageList.size()MessagePanelControl隐藏JDialog
公共无效更新视图(){ 同步(viewMPCSynchronizationObject){ Utils.throwExceptionWhenNotOnEDT();
JPanel messagesListPanel = new JPanel(); scrollPane.setViewportView(messagesListPanel); scrollPane.setBorder(null); messagesListPanel.setLayout(new BoxLayout(messagesListPanel, BoxLayout.Y_AXIS)); if (MessageHandler.getInstance().getMessages() != null && MessageHandler.getInstance().getMessages().size() > 0) { [...] //Create buttons, text icons... for each message [...] SwingUtilities.invokeLater(new Runnable() { public void run() { MainFrame().showMessageBoard(); } }); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { MainFrame().closeMessageBoard(); } }); } repaint(); }}
主框架:
//Show Messageboard
public void showMessageBoard() {
if (messageBasicPane != null) {
messageBasicPane.setVisible(true);
messageBasicPane.repaint();
}
}
[...]
//Close Messageboard
public void closeMessageBoard() {
if (messageBasicPane != null) {
messageBasicPane.setVisible(false);
}
}
此行创建JDialog,详细:
[...]
public LockPane(JFrame parentFrame, JComponent componentToShow, Dimension paneSize, float opacity, ModalityType modality) {
super(parentFrame, true);
Utils.throwExceptionWhenNotOnEDT();
createDialog(paneSize, opacity, modality);
if (componentToShow != null) {
add(componentToShow);
}
pack();
}
private void createDialog(Dimension paneSize, float opacity, ModalityType modality) {
Utils.throwExceptionWhenNotOnEDT();
setUndecorated(true);
setModalityType(modality);
if (opacity < 1 && opacity >= 0)
com.sun.awt.AWTUtilities.setWindowOpacity(this, opacity);
setSize(paneSize);
setPreferredSize(paneSize);
setMaximumSize(paneSize);
setBounds(0, 0, paneSize.width, paneSize.height);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
}
[...]
Java VisualVM 的一个新观察结果是,AWT-EventQueue 没有被阻塞,只是有时会有一小段“等待”但没有阻塞。另一个奇怪的事情是,有时我的JDialog 是完全透明的(不可见),有时它是白色的,具有所需的不透明度。
【问题讨论】:
-
问题在于,我们实际上需要查看代码:不是您的项目代码,而是您实际查看和消失的代码。好吧,试着做一个SSCCE
-
你说得对,我将涉及的代码提取到一个小概述中。据我所知,制作SSCCE是不可能的,因为问题与从不同部分/线程调用MessageHandler有关。
-
您的代码不是线程安全的;当其他线程调用
addMessage时,在 UI 线程上执行synchronized(viewSynchronizationObject)无济于事,这会在没有任何同步的情况下修改消息列表。 所有线程访问同一资源必须同步,否则毫无意义。请注意,在大多数情况下,如果 UI 未绘制但仍可点击,则表明在重绘过程中出现异常。 -
@Holger:感谢您的评论,您说得对,我更正了 addMessage 方法。没有发生异常,我一直在看堆栈跟踪,不幸的是那里没有异常
-
@alex:如果不知道
mpc.updateView()的真正作用,就不可能提供进一步的帮助。顺便提一句。我对你给invokeLater的false参数感到恼火。
标签: java multithreading swing