JavaWeb开发学习笔记之Servlet(三) Cookie与Session, 及Servlet线程安全问题

Cookie与Session

Cookie是服务器发送给浏览器的体积很小的纯文本信息,用户以后访问同一个Web服务器时浏览器会把它们原样发送给服务器。引用维基百科的解释:

指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)

cookie是识别当前用户,实现持久对话的最好方式,目前所有主流浏览器都支持,以至于Http协议都为其定义了新的Http头部。

Cookie的特点

  • cookie通过请求头/响应头在服务器与客户端之间传输,大小限制为4KB
  • 一台服务器在一个客户端最多保存20个cookie
  • 一个浏览器最多保存300个cookie
  • cookie的name和value都不能使用中文,如果需要使用中文,需要对中文进行URL编码,解码操作。

Cookie的应用

cookie一般有两个作用,识别用户身份和记录历史

  • 识别用户身份: 假设一个A用户访问了,a.com, 那么服务器就给这个用户A返回一个cookie,如[cookie=A], 当A再次访问时a.com时,请求中就会带上[cookie=A]这个cookie. 同理,用户B访问a.com时,服务器分配一个cookie,[cookie=B]给B用户,当B再次访问时,会在请求中带上[cookie=B]这个cookie。 这样,服务器就能够知道到底是A在访问还是B在访问了
  • 记录历史,常见的如购物车功能。假设一个用户A进入一个购物网站,讲a商品,b商品添加到购物车,那么这个购物网站就会给这个用户A返回一个cookie, 如:
    [cookie=A, cart=a,b],这样,当用户下次再进入这个网站时,会将这个cookie一并带入请求,就可以看到商品a,b依然躺在购物车里了。

Session

session是一种持久网络协议,在用户(或用户代理)端和服务器端之间创建关联,从而起到交换数据包的作用机制。在一段时间内,一个客户与Web服务器的一系列交互过程,称为”会话”,也就是session, session是一个比较抽象的概念,

我们今天常说的session,是为了绕开cookie的各种限制,通常借住cookie本身和后端存储实现的,一种更高级的状态实现。session是由服务器端生成的,存储在服务器端。

我们可以对session设置过期时间,单位是分钟,在web.xml中配置:

1
2
3
<session-config> 
<session-timeout>30</session-timeout>
</session-config>

需要注意的是我们如果在代码中设置session的过期时间,单位是,如:

1
session.setMaxInactiveInterval(int seconds)

tomcat的默认session过期时间为30分钟

Session与Cookie的比较说明

  • session在服务器端,cookie在客户端(浏览器)
  • session存储的数据量大,数据类型丰富。 cookie存储的数据量不超过4KB,且只能存储ASCII字符串。
  • 根据以上第一点,安全性方面session优于cookie. cookie在客户端中是可见的。
  • 跨域方面cookie优于session,cookie只要设置domain域更改即可。
  • 对于服务器的压力,session的压力大大高于cookie
  • 有效期的不同,只要关闭了浏览器session就会失效,不能完成信息永久保存,且如果session设置的超时过长,服务器累计的session就会越多,越容易招致内存溢出。

通常我们的做法是将用户的登录信息,如账号密码保存在session中,在cookie保存此session的session id,请求时让保存的session id随着cookie一同访问,通过session id的机智来获取session进行验证即可。

Servlet的线程安全问题

Servlet的线程安全问题

当Servlet容器接收到Client的Http请求时,容器会先从线程池中取出一个线程然后找到该请求对应的Servlet对象,并进行初始化(init), 再调用service()方法, 而当另一个客户端再次发出请求访问该servlet时,并不会再次初始化这个servlet,不会调用init,也就是说,多个线程在使用这一个servlet实例。JSP/Servlet容器默认采用的是单实例方式处理多个请求,这样设计的好处是减少产生Servlet实例的开销,提升了对请求的响应时间。对于Tomcat可以在server.xml中通过元素设置线程池中线程的数目。

也正因为此,我们不能在Servlet中修改成员变量,否则会引发线程安全问题

如何开发线程安全的Servlet

关于如何开发线程安全的Servlet,可以从以下几点入手:

  • 变量:
    将参数变量本地化,使用局部变量而不是成员变量。
  • 属性: ServletContext,HttpSession,ServletRequest对象中的属性.
    其中,ServletContext和HttpSession是线程不安全的,因为这两个对象可以同时被多个servlet对象共享。ServletRequest对象是线程安全的,对于每一个请求都会创建一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问,它是线程安全的。
  • 使用同步快Synchronized,防止可能异步调用的代码块。为多线程增加排队
  • 使用同步的集合类,使用Vector代替ArrayList,使用HashTable代替HashMap。
  • 不要再Servlet中创建自己的线程来完成某个功能
  • 在多个Servlet中对外部对象(如文件)进行修改操作一定要加锁,做到互斥的访问
  • 实现ServletThreadModel接口
    javax.servlet.SingleThreadModel接口是一个标识接口,如果一个Servlet实现了这个接口,那Servlet容器将保证在一个时刻仅有一个线程可以在给定的servlet实例的service方法中执行。将其他所有请求进行排队。
1
2
3
public class xxx extends HttpServlet implements SingleThreadModel{
// 这种方法会产生大量系统开销,尽量不要用。SingleThreadModel已废弃
}

如果使用了SingleThreadModel接口,服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。 此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。SingleThreadModel接口在servlet规范中已经被废弃了

参考与引用:

知乎:cookie是什么

知乎:认识HTTP—-Cookie和Session篇

博客园:Servlet的多线程和线程安全

知乎:Servlet与多线程的关系是什么