从客户端减少服务器压力的方法以及实现(模态框,swing,线程)

用户常常有一个习惯,那就是在手机或电脑卡的时候多次点击,我们知道,通常视图界面是一个单另的线程案例,而网络这边是另一个线程,卡是因为我们的服务器端向客户端发送响应速度慢,而这时候视图界面还是运行态,因此用户的多次点击是一种无效而且对服务器增加压力的一种体现。
为什么需要一个进程显示视图,另一个线程做网络传输,这是因为我们不可能发送请求后就令客户端阻塞,而且等待响应回来的时候才唤醒客户端线程,这无疑是对资源的低效利用,在发送后,客户端线程往往还需要做别的操作,这样才是对资源的有效利用。(就是qq你点了个登陆以后变成正在登陆了,你点不了登录了,然后qq后台还在计算你的很多数据。)
而且请求一次并未等到有响应了才会发下一条请求,因此产生了多线程同步的问题。
下图说明了单次请求和响应还有模态框之间线程关系。
在这里插入图片描述
模态框可以解决上述问题,本文以登录为案例进行叙述,用户点击按钮后弹出模态框,禁止用户再次点击任何按钮,在响应返回时,也就是客户端在开始处理后台发送的响应时,关闭模态框。问题看似简单,然而在多线程的条件下会出现很多线程同步问题,后面会一一介绍。
首先看一下我们要用的api:JDialog
swing中常见的一个类,在父窗口弹出一个新窗口,令父窗口无法被限定。
特性:阻塞当前线程,也就是在dialog.showDialog();下面的代码是无法执行的,且!
并不会释放锁。也就是说如果你在synchronized 锁中要是调用了这句话,就会造成线程的阻塞并且不释放锁。
这样生产者消费者模式就无法解决该类问题,原因就是弹出模态框会阻塞线程,而这种生产者消费者模式需要在弹出模态框后对信号量进行处理,然而Dialog的阻塞并不释放锁的特性令我们无法进行处理。
那我们来思考一下,模态框的显示和关闭应该是哪里来进行,应当都在客户端,只不过是不同的线程,分析一下有哪些线程,
1.页面线程(swing)线程
2.发送请求的线程
3.客户端处理来自服务器端响应的线程。
显示模态框的线程应该是2,发送请求后跳出模态框阻塞。
让2唤醒的线程应该是3。
1线程始终保持着正常运行。
现在我们让页面异步发送两个请求
在这里插入图片描述
基本的数据传送就是这样,我也写了,然而根本不能完成该需求。会出现模态框根本不会消失的错误问题。这很奇怪,一个显示模态框,一个关闭模态框,是不应该出现这种问题的,刚开始想到了JMM的一些特性,加了许多的voliater然而还是不能解决问题,经过不断地尝试和查看内存信息,最终发现了原因。
原因分析:首先说两点,1.网络传输是耗时的且时间浮动很大。2.打开模态框和关闭模态框是两个线程,然而打开模态框的操作并非是能在一个时间片段内完成!看下源码
在这里插入图片描述
如此一大段代码是未必能在一个时间片段里完成的,这会出现一个问题,网络速度如果很快,我打开模态框到一半了,上下文切换了,关闭了模态框,上下文再次切换到打开模态框,然后模态框就再也不会消失了。如果再快一点,发送请求后直接客户端接受相应处理线程直接就关闭模态框了,发送请求端才开始执行showDialog语句。快会让我们的模态框无法关闭。
2.发送响应接受请求如果在多线程的环境下,可能出现以下情况:发送了两个请求,返回了两个响应,响应顺序与发送顺序不一定是相同的。
针对以上两种问题做出以下处理。
针对一,新开启线程去显示模态框,这样就不会阻塞我们的线程,但这也增加了线程数量,增大了编程难度。
针对二,我们把response与模态框绑定在一起,根据response来关闭模态框,让我们的程序可以处理请求和相应不对应问题。
在发送请求的线程中开启新的子线程来显示模态框。

    public class WaittingDialog implements Runnable {
	/**
	 * 和客户端发送线程是同一个线程,且在客户端发送发送请求之前完成了模态框显示线程的
	 * @param dialog
	 * @param response
	 */
	public WaittingDialog(MecDialog 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令下述的dialog操作具有原子性,因为对dialog的读操作在客户端处理响应线程   
		//中运用了相同的锁来完成互斥。
		synchronized (ClientConversation.class) {
		//这是未必能取到的,因为如果服务器端返回过快会删除掉之中的Dialog
			dialog = ClientConversation.getDialogLock(response);
			if (dialog == null) {
				return;
			}
			dialog.setGetByShow(true);
		}
		
		if (dialog != null) {
			//阻塞该线程,但Swing线程中的代码还在继续执行
			dialog.showDialog();
		}
	}
}

在我们创建好了模态框和响应的映射的时候加入map,注意在令该线程进入就绪态时请求可能已经发送出去了,更有可能响应已经回来了。
下面是我们在处理服务器响应的线程中需要执行的代码。

public void dealResponse() {
		String action = message.getAction();
		String parameter = message.getMessage();
		//互斥锁
		synchronized (ClientConversation.class) {
			MecDialog dialog = ClientConversation.dialogMap.get(action);
			//利用自旋CAS来等待到模态框已经成为了活动窗口,当模态框显示了以后
			//就结束自旋
			if (dialog.isGetByShow()) {
				boolean isActive = false;
				while (!isActive) {
				       //window的方法javaapi1.6:返回此窗口是否为活动窗口。
				//MecDialog enxtends JDialog extends Dialog extends window
					isActive = dialog.isActive();
				}
			}
			dialog.closeDialog();
			ClientConversation.dialogMap.remove(action);
		}
		...........处理响应的具体操作

}
我们具体模态框的假类图,volatile保证了线程间的通讯。
在这里插入图片描述
对于锁的应用是很重要的,这里运用锁达到了原子性和有序性,简化了编程的逻辑性。
对于客户端发送请求就是放在map中,看map里有没有Dialog,然后发请求,然后根据map里头有没有dialog来显示模态框。
对于客户端处理响应,就是看有没有显示,没有显示就map中删除掉模态框,令客户端发送线程请求就不显示模态框了,有就等待模态框出来再关闭。
逻辑没有问题了,经过不断地测试,确实也是没问题了。对于多线程的编程难度还是很大的。
指导者:微易码科技

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值