Java中对于模态框的使用以及在多线程中的应用

模态框可以通过继承JDialog并且设置构造参数boolean modal = true即可。

但是 当此模态框setVisible(true)时候,会导致当前线程阻塞。

那么问题来了:现有一需求是点击登陆按钮,则会向服务器发送登陆请求以及获取数据的请求,待服务器成功返回响应即可登陆主界面。但是由于如果有人多次点击登陆按钮,就会发很多次请求,这样服务器的压力就会变大。如何改进程序较少服务器的压力呢?

分析:方法有很多,服务器可以判断响应请求是否重复从而减少多余的响应,客户端可以让用户点击完按钮后,使得按钮不可再点,从而较少多余的请求。相比之下,第二种方法是从根本解决问题的。所以在这我选择第二中方法。

思路:在点击登陆后弹出模态框,等待响应回来后自动关闭模态框。

实现

首先对于模态框可以是通过继承JDialog并设置参数得到。

public class MyDialog 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;
	
	private volatile boolean getByShow;
	
	public MyDialog(Frame owner, boolean modal) {
		super(owner, modal);
		
		getByShow = false;
		container = getContentPane();
		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		setUndecorated(true);
	}
	
	MyDialog(Dialog owner, boolean modal) {
		super(owner, modal);
		
		container = getContentPane();
		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		setUndecorated(true);
	}

	public boolean isGetByShow() {
		return getByShow;
	}

	public void setGetByShow(boolean getByShow) {
		this.getByShow = getByShow;
	}

	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;
	}
	
	void showDialog() {
		setVisible(true);
	}
	
	void closeDialog() {
		dispose();
	}

}

由于模态框setVisible(true)时候,会导致当前线程阻塞。所以为了之后可以关闭模态框,我们使用线程去控制模态框的开启。

public class WaittingDialog implements Runnable {
	
	public WaittingDialog(MyDialog dialog, String response) {
		ClientConversation.putDialogLock(response, dialog);
		new Thread(this, "WD-" + response).start();
	}
	
	@Override
	public void run() {
		String response = Thread.currentThread().getName().substring(3);
		MecDialog dialog = null;
		synchronized (ClientConversation.class) {
			dialog = ClientConversation.getDialogLock(response);
			if (dialog == null) {
				return;
			}
			dialog.setGetByShow(true);
		}
		
		if (dialog != null) {
			dialog.showDialog();
		}
	}
}

WaittingDialog构造中需要两个参数,第一个是我们的模态框,第二个是点击按钮后的响应Action。在构造中将响应与dialog加到客户端会话层中的map中的原因是:当登陆请求发到服务器会话层,服务器会话层返回相应给客户端会话层后,通过action取得对应的dialog从而关闭相应dialog。

下面是客户端通过客户端会话层发送请求的部分代码:

	public void sendRequest(String request, String response, String parameter,
			RootPaneContainer parentView, String message) {
		if (parentView instanceof Frame) {
			new WaittingDialog(
				new MecDialog((Frame) parentView, true)
				.initDialog(message), response);
		} else if (parentView instanceof JDialog) {
			new WaittingDialog(
				new MecDialog((JDialog) parentView, true)
				.initDialog(message), response);
		}
		sendRequest(request, response, parameter);
	}

	public void sendRequest(String request, String response, String parameter) {
		conversation.send(new StandardNetMessage()
				.setFrom(conversation.getId())
				.setTo(INetConstance.SERVER)
				.setCommand(ENetCommand.REQUEST)
				.setAction(request + ":" + response)
				.setMessage(parameter));		
	}
	

服务器会话层接受请求并返回客户端会话层部分代码:

case REQUEST:
			String action = message.getAction();
			int commaIndex = action.indexOf(':');
			String request = action.substring(0, commaIndex);
			String response = action.substring(commaIndex + 1);
			String parameter = message.getMessage();
			
			if (this.action != null) {
				try {
					Object result = this.action.dealRequest(request, parameter);
					String returnString = gson.toJson(result);
					send(new StandardNetMessage()
							.setFrom(INetConstance.SERVER)
							.setTo(id)
							.setCommand(ENetCommand.RESPONSE)
							.setAction(response)
							.setMessage(returnString));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			break;

客户端会话层处理响应部分代码:

		case RESPONSE:
			String action = message.getAction();
			String parameter = message.getMessage();
			
			synchronized (ClientConversation.class) {
				MecDialog dialog = dialogMap.get(action);
				if (dialog.isGetByShow()) {
					boolean isActive = false;
					while (!isActive) {
						isActive = dialog.isActive();
					}
				}
				dialog.closeDialog();
				dialogMap.remove(action);
			}
			
			client.getAction().dealResponse(action, parameter);
			break;

问题来了:由于模态框是由线程控制的,这样若不加相应的同步控制,可能会发生无法关闭模态框问题,如点击登陆按钮后,模态框线程启动进入就绪态,并未到执行态,模态框还未显示时,服务器响应已经返回给客户端,客户端执行相应的关闭模态框操作,然后模态框线程就入执行态显示模态框,这样就关闭不了模态框了。

分析方法:使用同步块使得从map中取dialog互斥,并且给MyDiag加入一个volatile 的标志getByshow。此时有两种情况:1.在控制模态框的线程(得到锁)取得dialog后判断为空?return null :dialog.setGetByShow(true)然后释放锁。如果此时服务器已经返回响应,并且(得到锁)取得dialog,判断标志为真后,继续等待dialog为active后再关闭dialog,从map中移除该dialog然后(释放锁)。2服务器已经返回响应,并且(得到锁)取得dialog,判断标志为假后直接执行关闭dialog,并且移除并(释放锁)。控制模态框的线程(得到锁)发现取到dialog为null则直接return,不执行模态框的显示。

具体实现请看以上代码,更多细节请查看我的其他文章。

猜你喜欢

转载自blog.csdn.net/gary0917/article/details/84262746