servlet单实例多线程分析
servlet的生命周期由web容器负责,以tomcat作为服务器时,servlet作为组件交给tomcat管理。当浏览器客户端第一次发送请求到服务器端的时候,tomcat会创建一个全新的servlet实例来处理该请求,并创建一个新的线程,当客户端再次发送相同的请求的时候,便不会再实例化该servlet,而是创建一个新的线程,来访问该servlet。这是servlet单实例多线程的原理。
单实例的好处是:减少了servlet实例的消耗,有利于节省内存,节约资源,在客户端频繁访问服务器的时候,加快了服务器的响应速度。
多线程可能会造成的问题:线程安全问题,当多个线程同时访问一个servlet的时候,并且访问同一个资源的时候,可能会对数据造成不安全的影响。
处理多线程并发的安全问题
多线程机制
当tomcat容器启动时,servlet就被加载实例化到容器中。容器初始化时servlet读取配置文件,加载到配置文件中的线程池中设置的线程数目,存放在线程池中。当客户端有请求达到servlet时,servlet通过调度线程调度该线程池中等待执行的工作线程给当前请求,并处理其相应内容,请求结束后,工作线程又被放回线程池中。
Java内存模型对多线程的处理
在Java内存模型(JMM)中,系统存在每个程序的一个主内存,所有的变量存储在主内存中,每个线程中会有主内存中的共享变量的副本,该线程可以操作共享变量的副本。而在各自的线程中,对主内存是不可见的,每个线程的数据不是立即写回内存,JMM中存在一个缓存区,会对数据进行缓存。在每个线程中的局部变量和其他线程之前也是互不干扰的。
Java代码层面解决方法
在servlet中不使用实例变量,可以将变量写在各自的方法内,使用局部变量。
实现 SingleThreadModel 接口或在同步代码块使用synchronized,但是这两种方法对内存的消耗过高,不利于性能。
@Transactional注解,可设置四种事务隔离级别,解决事务的原子性问题,当事务出错的时候,可以回滚事务,避免数据操作错误。
JUC并发处理。
ThreadLocal可以为当前线程开辟一个新的空间,用于存放临时数据,但只对当前服务器可见,其他服务器无法访问。
业务层代码逻辑控制。
数据库层面解决问题
数据库设置访问权限。
利用数据库的锁机制,读写锁对数据进行控制。
数据库MVCC锁实现多版本并发控制。
多线程并发情况举例
同一用户同时请求访问同一个资源
避免用户在多个地方登录,先做登录校验。
业务层代码逻辑判断当前资源是否合理存在。
不同用户同时请求访问同一个资源
JUC并发处理。