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,不執行模態框的顯示。
具體實現請看以上程式碼,更多細節請檢視我的其他文章。