JDialog模态框阻塞线程的妙用

2019-04-14 18:35发布

JDialog是swing的一个组件。他有两种用法。 第一种就是使用系统提供的静态方法。在JOptionPane中提供了四种静态方法: 方法名  描述 showMessageDialog 消息对话框,向用户展示一个消息,没有返回值。 showConfirmDialog 确认对话框,询问一个问题是否执行。 showInputDialog 输入对话框,要求用户提供某些输入。 showOptionDialog 选项对话框,上述三项的大统一,自定义按钮文本,询问用户需要点击哪个按钮。             这四种方法重载了很多次,有不同的参数。可根据需要选用。 第二种就是继承JDialog,自定义一个模态框。今天,我们就来着重介绍下自定义一个模态框,阻止用户登录的例子。   众所周知,用户在登录某个系统的时候,输完账号密码后点击登录,如果没有反应,对服务器方面知识空白的绝大多数用户会认为这是卡住了,可能会多次点击登录。但事实上每一次点击都会向系统发出请求,服务器不能及时处理,就是因为业务繁忙或者网络故障等等,如果用户多次请求,服务器就要多次响应。  如图是我编写的一个登录界面,当我连续点击三次登录按钮后,会有如下输出:  如图,客户端连着向服务器发送了三次连接请求,也就意味着服务器需要处理这三次请求。 近些年来,为了减轻服务器的工作压力,推出了不少方案,比如分布式服务器等等。但是如果能从源头上减轻服务器的负担,岂不是美滋滋。如果能有效阻止用户向服务器多次发送没有意义的请求,就能有效地从源头减轻服务器的负担。 JDialog模态框,有一个boolean类型的参数modal,这个参数就是控制模态框显示之后,还能不能对其父窗口进行操作,如果选择为false,则该模态框为非模态的,在弹出模态框后还能继续对其父窗口操作。如果选择为true,弹出模态框后不能对父窗口进行操作。我们要用的就是这种模态的。 首先,自己编写一个继承了JDialog的模态框,然后再编写一个类,该类中产生一个线程,在线程中调用模态框。 package com.mec.server_client.common; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dialog; import java.awt.Font; import java.awt.Frame; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.TitledBorder; public class MecDialog extends JDialog { private static final long serialVersionUID = 2309852253785194778L; private static final String TITLE = "晨哥温馨提示"; private static final Color topicColor = new Color(0, 0, 0); private static final Font normalFont = new Font("宋体", Font.PLAIN, 16); private static final Color backcolor = new Color(0x88, 0x88, 0x88); private static final int PADDING = 15; private Container container; public MecDialog(Frame owner, boolean modal) { super(owner, modal); container = getContentPane(); setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); setUndecorated(true); } MecDialog(Dialog owner, boolean modal) { super(owner, modal); container = getContentPane(); setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); setUndecorated(true); } public MecDialog initDialog(String message) { JPanel jpnlBackground = new JPanel(new BorderLayout()); container.add(jpnlBackground); TitledBorder ttbdDialog = new TitledBorder(TITLE); ttbdDialog.setTitleColor(topicColor); ttbdDialog.setTitleFont(normalFont); ttbdDialog.setTitlePosition(TitledBorder.TOP); ttbdDialog.setTitleJustification(TitledBorder.CENTER); jpnlBackground.setBorder(ttbdDialog); jpnlBackground.setBackground(backcolor); JLabel jlblMessage = new JLabel(message, JLabel.CENTER); jlblMessage.setFont(normalFont); jlblMessage.setForeground(topicColor); jlblMessage.setSize(message.length() * normalFont.getSize(), normalFont.getSize() + 4); jpnlBackground.add(jlblMessage, BorderLayout.CENTER); int height = 5 * PADDING + jlblMessage.getHeight(); int width = 10 * normalFont.getSize() + jlblMessage.getWidth(); setSize(width, height); jpnlBackground.setSize(width, height); setLocationRelativeTo(null); return this; } public void showDialog() { setVisible(true); } public void closeDialog() { dispose(); } } package com.mec.server_client.common; public class WaittingDialog implements Runnable { private volatile MecDialog dialog; public WaittingDialog(MecDialog dialog, String response) { this.dialog = dialog; ClientConversation.putDialogLock(response, dialog); new Thread(this, "Waitting Dialog").start(); } @Override public void run() { dialog.showDialog(); } } 在客户端需要向服务器发送用户的登录请求时,启动该线程,在客户端显示这个不可关闭的模态框,然后在客户端收到服务器的响应后释放客户端的屏幕。以此来保证当用户点击登陆后,服务器未及时响应时,阻止用户多此点击登录按钮的操作。  说起来挺简单的,但是在实现过程中还遇到了一些麻烦。 在此先说一下关于线程的相关事宜。线程在调用后(start之后),并不是直接启动,而是让线程进入就绪态,竞争cpu,在竞争到cpu之后线程才能运行(run方法运行)。 那么在启动了模态框之后,也就是运行了waittingDialog的构造方法,在线程进入就绪态竞争cpu还没有真正运行的时候,访问服务器的操作已经完成,并且接着执行了关闭模态框的方法,然后run()方法才开始运行,启动模态框,那么启动了之后就无法关闭了。 这个问题的难点就在调用了模态框之后这个线程就阻塞了,无法在执行后续代码。因此无法在服务器返回响应后执行关闭模态框的方法。 在经过多此尝试后,总结出了解决问题的方法。那就是isActive()方法。这个方法可以检测一个线程的run()方法是否运行。因此可以在客户端处理服务器返回响应的时候检测waittingDialog线程的run()方法是否运行,用一个循环持续检测,当检测到该run()方法运行后,再继续执行后续代码,关闭该模态框。 如果出现刚刚的问题,当还未运行run()方法,服务器就返回处理结果时,进入检测, 持续循环,知道模态框被调用后才能继续往下执行。 MecDialog dialog = dialogMap.get(action); if (dialog != null) { while (!dialog.isActive()) { ; } dialog.closeDialog(); dialogMap.remove(action); }