web开发必须知道--servlet、连接池、线程池的概念原理和使用

来源:互联网 发布:镇江市迈越网络 编辑:程序博客网 时间:2024/05/23 13:12

servlet定义

全称Java Servlet,是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者,广义的Servlet由支持servlet(具有servlet引擎)的web服务器调用和启动运行。(HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。)

servlet接口

servlet是一个接口,它有两个方法分别返回ServletRequest和ServletResponse对象,它由同样是一个接口(GenericServlet)实现,这个接口除了实现servlet接口外,还实现另外两个接口ServletConfig和Serialzable。分别配置selvet和提供串行化。而HttpServlet类继承了GenericServlet,并实现了返回HttpServletRequest和HttpServletResponse对象的方法。(HttpServletRequest和HttpServletResponse是基于Http协议升级改造,提供对Cookie、request(response)headers、Session、URL操作方法)。

servlet执行过程(生命周期)

servlet容器(tomcat)运行:

1Web服务器首先加载Servlet。
2装载并创建该Servlet的一个实例对象。 
3调用Servlet实例对象的init()方法。

(当servlet容器(tomcat),运行后就创建servlet对象。(Servlet是单例多线程),并调用init()方法初始化。只调用一次。)

client请求到达:
创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。service()方法根据请求方式调用doGet(HttpServletRequest,HttpServletResponse)或者doPost()方法,来处理请求和并响应。

Servlet容器(web服务器-tomcat)关闭:
WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。 

TOMCAT配置servlet

我们都知道在客户端请求服务器过程中,只创建一个servlet实例对象以及只调用一次init()方法。但web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。

线程安全问题在这里不讲,因为在另一篇文章java多线程就讲过了

我们主要考虑的问题是:如果百万个客户端同时访问服务器时,如果为每一个客户端创建一个线程,那么将创建百万个线程,你觉得可能吗?cpu肯定吃不消。而且一个客户端请求完毕还要关闭呢,也就是回收分配给这个客户端的线程。这样创建和销毁线程过程中,耗费了时间和资源。

解决办法

由于Servlet是单例多线程,每个请求都创建一个线程,可以配置线程池的线程数量?

java中线程池内部情况

Executor 由ExecutorService实现
ExecutorService由AbstractExecutorService实现 
AbstractExecutorService由ThreadPoolExecutor实现
方法:
execute()
submit()与execute同样都是提交,但它有返回结果
shutdown()关闭
shutdownNow()
getQueue()取队列
getPoolSize() 得到线程池大小
getActiveCount()活动线程
getCompletedTaskCount()已经执行完的任务数
setCorePoolSize()动态调整线程池的大小
setMaximumPoolSize()动态调整线程池最大线程数
Executors.newCachedThreadPool();创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();创建容量为1的缓冲池
Executors.newFixedThreadPool(int); 创建固定容量大小的缓冲池
注意:线程对象.interrupted() 发出中断该线程,如果该线程执行sleep()就抛出异常,和lsinterrupt()可以监听是否发生中断事件,volatile 定义的变量表示他们访问的是同一块内存,
 线程池作用:为了防止多次创建和删除线程,节省消耗,和时间。还有就是可以控制线程数和并发数量
  线程池的状态:
  1 running
  2 shutdown 不接受新任务 等待正在执行的的任务结束
  3 shutdownNow (stop)不接受新任务,并且去尝试终止正在执行的任务
  4 当线程池处于shutdown或者stop状态,并且所以的工作线程已经销毁,任务缓存队列已经清空或者结束,就把线程池的状态设置为TERMINATED状态。
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
   拒接任务策略
   1 由再次提交(减缓提交任务速度)
   2 丢弃任务
   3 把队列中第一个进入的队首丢弃,把新的加入
  任务缓存 队列策略:
   不管是何种类型的对列如果超过了最大线程数就发生拒接任务或者异常策略。队列是采用解锁的方式的
   所以一次只能执行删除或者添加,不能同时执行添加和删除
   1 使用直接提交队列
    当新任务无法添加到队列中时,就先使用线程池中的空闲线程。当当前运行的线程数大于线程池规定的线程数时(即线程
    池中没有空闲线程了),就创建新的线程来运行新任务,(这时应该把最大线程数设置为无线的)
    2 无界队列
    新任务来时,如果线程池中还有空闲线程就让它来执行新任务,没有就把新任务加入到队列中(等于是运行线程数等于或者小于线程池规定的线程数量),最大线
    程数用不上。
    3 有界队列
    设置最大的线程数,大于就发生拒接 
    创建线程:
    使用工厂模式创建和管理线程

java线程池实现

1创建线程池对象 

2 提交任务(一个任务就是一个线程,任务处理方式通过线程把处理对象通过构造函数传进去,再在run方法中调用对象的方法)

提交的任务有以下几种处理方式进入任务队列或者拒绝接受任务或者直接使用线程执行任务 

满足就创建通过工厂方法创建线程并在线程中run方法执行任务

2关闭线程池对象

如何开发Servlet线程安全

1 变量

使用局部变量,多线程对局部变量是不共享的

使用同步块Synchronized

2 属性

ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。
HttpSession:(线程是不安全的)
HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。
当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。
这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。
ServletRequest:(线程是安全的)
对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。
注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。

3使用安全集合

使用Vector代替ArrayList,使用Hashtable代替HashMap。

4 不要在servlet再创建线程

Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化,出现多线程安全问题

5 多个servlet时,它们对外部对象进行修改操作要加锁。

javax.servlet.SingleThreadModel接口是一个标识接口,如果一个Servlet实现了这个接口,那Servlet容器将保证在一个时刻仅有一个线程可以在给定的servlet实例的service方法中执行。将其他所有请求进行排队,当使用多个servlet类时,它不能解决并发问题,已经废弃了。

如何配置线程池的线程数

线程数调优
前面的示例展示了如何创建和使用线程池,但是,使用线程池的核心问题在于应该使用多少线程。首先,我们要确保达到线程上限时,不会引起资源耗尽。这里的资源包括内存(堆和栈)、打开文件句柄数量、TCP连接数、远程数据库连接数和其他有限的资源。特别的,如果线程任务是计算密集型的,CPU核心数量也是资源限制之一,一般情况下线程数量不要超过CPU核心数量。
由于线程数的选定依赖于应用程序的类型,可能需要经过大量性能测试之后,才能得出最优的结果。当然,也可以通过增加资源数的方式,来提升应用程序的性能。例如,修改JVM堆内存大小,或者修改操作系统的文件句柄上限等。然后,这些调整最终还是会触及理论上限。
利特尔法则
利特尔法则 描述了在稳定系统中,三个变量之间的关系。
L=λW
其中L表示平均请求数量,λ表示请求的频率,W表示响应请求的平均时间。举例来说,如果每秒请求数为10次,每个请求处理时间为1秒,那么在任何时刻都有10个请求正在被处理。回到我们的话题,就是需要使用10个线程来进行处理。如果单个请求的处理时间翻倍,那么处理的线程数也要翻倍,变成20个。
理解了处理时间对于请求处理效率的影响之后,我们会发现,通常理论上限可能不是线程池大小的最佳值。线程池上限还需要参考任务处理时间。
假设JVM可以并行处理1000个任务,如果每个请求处理时间不超过30秒,那么在最坏情况下,每秒最多只能处理33.3个请求。然而,如果每个请求只需要500毫秒,那么应用程序每秒可以处理2000个请求。
拆分线程池
在微服务或者面向服务架构(SOA)中,通常需要访问多个后端服务。如果其中一个服务性能下降,可能会引起线程池线程耗尽,从而影响对其他服务的请求。
应对后端服务失效的有效办法是隔离每个服务所使用的线程池。在这种模式下,仍然有一个分派的线程池,将任务分派到不同的后端请求线程池中。该线程池可能因为一个缓慢的后端而没有负载,而将负担转移到了请求缓慢后端的线程池中。
另外,多线程池模式还需要避免死锁问题。如果每个线程都阻塞在等待未被处理请求的结果上时,就会发生死锁。因此,多线程池模式下,需要了解每个线程池执行的任务和它们之间的依赖,这样可以尽可能避免死锁问题。
即使没有在应用程序中直接使用线程池,它们也很有可能在应用程序中被应用服务器或者框架间接使用。 Tomcat 、 JBoss 、 Undertow 、 Dropwizard 等框架,都提供了调优线程池(servlet执行使用的线程池)的选项。

TOMCAT配置线程池

同时我们在处理请求和产生响应时,可能需要访问数据库,所以就需要经历建立连接和销毁过程,同样会浪费时间和资源。

解决办法:

连接池 

建议看http://4006a02d.nat123.net/

TOMCAT配置连接池

0 1