void pushTask(Task task);//将任务放入任务队列的队尾
Task popTask();//从任务队列的队头取出任务执//行并从队列中删除该任务 void RemoveTask(Task task);//从任务队列中移走任务 }
TaskQueue代表一个任务队列,其作用是提供一种缓冲机制,将没有处理的任务放在任务队列中,等到有工作线程可用时依次执行队列中的任务。
interface Task{
void start();//任务开始执行 void reject();//丢弃该任务 }
Task是一个接口,它是每个任务必须实现的接口,线程池只执行实现了该接口的类,它提供一组方法用来规定执行任务的入口、任务的执行状态、任务执行完后的收尾工作等,从而保证线程逻辑和应用逻辑的分离,工作线程通过该接口调度任务的执行。
3.1.2.2流程图
线程池处理任务的过程如图3.2所示。 3.2.2.3存在的问题
线程池为我们构建多线程的服务器应用程序提供了强大的机制,但使用它可能会出现一些问题。
1.资源不足问题
这里的资源包括线程自身所使用的资源、线程处理任务时可能需要的其它资源,例如数据库连接、Socket套接字或文件输入输出流等。资源不足问题的本质原因就是过多创建线程从而消耗包括内存和其它系统资源在内的大量资源。
为解决资源不足问题,我们在线程池中提供了几个参数供用户根据实际情况优化线程池。
(1)maxThreads:线程池中最大线程数,默认为无限大。通常我们期望 maxThreads应该是服务器能够接受的最大并发访问数。如果有多于maxThreads的并发请求,多余的请求将被任务队列进行缓存。
图3.2线程池处理任务的流程图
(2)minTreads:线程池中最小线程数,默认为1。服务器启动时,会初始化值为minTreads的线程数。
(3)maxQueueCount:任务队列中能够缓存的最大请求数,默认为10。当任务队列已满时,多余的请求将被服务器拒绝。
(4)keepAliveTime:线程池中空闲线程能够保持的最大时间,默认为-1(表示永不过期)。KeepAliveTime保证了当并发访问数较低时,一些空闲的线程能够被服务器终止,从而被垃圾回收器回收。
2.线程泄漏问题
线程池中可能出现的另一个问题是线程泄漏问题。所谓线程泄漏,是指当从线程池中取出一个线程以执行一项任务,而在任务完成后该线程却没有返回池中。一个任务在长时间运行后不可避免会产生一些诸如连接超时或中断的情况,从而抛出一个运行时的异常或错误。如果线程池没有捕捉到它们,那么这样的线程就会直接退出,从而线程池中线程的数目将会永久减少一个。当这种情况发生的次数越来越多时,线程池最终就会为空,从而服务器无法为后续请求提供服务,因为池中已经没有可用的线程来处理任务。
对于线程泄漏问题,我们在工作线程中监视任务对象的运行状况,如果发生错误,通过errorOccur()方法通知线程池进行相应的处理,从而保证所有的工作线程运行正常。
3.2 JSP引擎
JSP给开发者提供了两个重要的能力:(1)它提供了访问远程数据的机制如EJB、JDBC、远程方法调用(Remote Method Invocation,RMI);(2)它让开发者从表现中封装和分离程序逻辑而达到最大程度的代码重用和灵活性。逻辑与表现的分离是它超越其它Web应用结构的一个主要优势。
JSP引擎主要功能是处理JSP文件。当Servlet容器发现用户请求的是一个JSP页面时,它将该请求交给JSP引擎进行处理。因此,可以说在响应请求的过程中JSP引擎是由Servlet容器调用的。我们将JSP引擎的入口程序设计成一个Servlet,该Servlet是JSP引擎的主导程序,包括对JSP引擎的初始化以及调用其它相关类对JSP页面进行处理。
JSP引擎处理JSP文件包括三个大的步骤:
(1)判断该JSP文件对应的Servlet实例是否存在并有效,如果条件满足,直接返回原实例,否则,进行下一步,该过程称为JSP的预处理;
(2)解析JSP文件生成Java文件;
(3)将Java文件编译成Java类,生成的Java文件必须实现
javax.servlet.jsp.HttpJspPage接口,HttpJspPage继承自Servlet接口,因此最终生成的Java类实际上就是一个Servlet。
上述过程如图3.3所示。
图3.3 JSP引擎处理JSP的过程
3.2.1 JSP的预处理
JSP的预处理的作用是判断请求的JSP文件是否已经编译成了Servlet且该Servlet是否是一个有效的Servlet。如果是第一次请求该JSP,则请求JSP解析器对JSP进行解析;如果该JSP曾经被解析过,按照如下规则决定是否需要对该JSP进行重新解析和编译:
1.JSP源代码改变时,重新解析和编译页面;
2.JSP页面中包含的其它页面的源代码改变时,重新解析和编译页面; 3.内存中代表该JSP的Servlet对象不存在时,重新解析和编译页面; 4.原来生成的Servlet类不再存在时,重新解析和编译页面; 5.除去以上四种情况,不再重新解析和编译页面。
对JSP的进行预处理对于提高服务器的响应性能具有重要意义。因为如果一个JSP从未改变,如果每次请求该JSP时,JSP引擎均重新解析和编译该JSP,不仅浪费系统资源,而且延长了对客户端的响应时间。 3.2.2解析JSP
JSP文件的解析是JSP引擎工作的核心。一个JSP页面由静态数据和动态数据组成,动态数据已经是Java代码,所以它们不必修改就可以使用,动态数据被顺序放进生成的Servlet的_jspService方法中。静态数据嵌入到_jspService方法中的javax.servlet.jsp.JspWriter实例的write方法中。
解析JSP的过程实际上就是对JSP源代码解析后,按照JSP规范组合成一个Servlet源文件的过程。
JSP引擎使用JspReader、JspParser、ServletWriter等几个类来实现对JSP文件的解析。
class JspReader{
int read(); void unread(int c);
void setLineNumber(int lineNumber); int getLineNumber(); }
JspReader是对java.io包中的PushbackReader和LineNumberReader的封装,具有读出(read)或回退(unread)单个字符和字符数组以及定位当前JSP文件行数的功能。
class JspParser{ Document parse(); Node addTag();
Node addIncludeDirective(); Node addScriptlet(); Node addExpression(); Node addText(); }
JspParser预先定义一组JSP的标准标签,并调用JspReader来读取JSP文件内容,根据JSP的标准标签,将读出的文件内容封装成一个XML Document对象。
class ServletWriter{ void writePageBegin(); void writeDeclaration(); void writeJspServiceBegin(); void writeImplicitObjects(); void writeMainSection(); void writeJspServiceEnd(); void writePageEnd(); }
ServletWriter根据生成的XML Document对象并结合JSP规范生成一个Servlet源文件。