1. 程式人生 > >Java中對於模態框的使用以及在多執行緒中的應用

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,不執行模態框的顯示。

具體實現請看以上程式碼,更多細節請檢視我的其他文章。