Servlet 是否是线程安全的 Spring MVC 线程是否安全

Servlet是单例多线程的无需置疑;多线存在共享实例时易发生线程不安全问题

单例多线程:一个线程中只有一个实例对象,但是如果在多线程的环境中,就会出现多个实例的情况,这样就不是单例模式了,
此时就容易发生线程不安全问题。
线程不安全问题详解:
多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。

根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。
每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,
缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;
堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。


 

 

每个Servlet会根据请求来新建线程

也就是说你接到一个请求到Servlet,这个Servlet就会运行一个Tread

 

 

 

Java线程的两个特性,可见性和有序性。

多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。拿上面的例子来说明,在多个线程之间共享了Count类的一个对象,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存count对象的一个副本,当线程操作count对象时,首先从主内存复制count对象到工作内存中,然后执行代码count.count(),改变了num值,最后用工作内存中的count刷新主内存的 count。当一个对象在多个工作内存中都存在副本时,如果一个工作内存刷新了主内存中的共享变量,其它线程也应该能够看到被修改后的值,此为可见性。

 

多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,

一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。

 

Spring mvc 线程不安全的原因
请求时多线程请求的,但是每次请求过来调用的Controller对象都是一个,而不是一个请求过来就创建一个controller对象

原因就在于如果这个controller对象是单例的,那么如果不小心在类中定义了类变量,那么这个类变量是被所有请求共享的,

这可能会造成多个请求修改该变量的值,出现与预期结果不符合的异常

在单例的情况下 相当于所有类变量对于每次请求都是共享的,每一次请求对类变量的修改都是有效的

那有没有办法让controller不以单例而以每次请求都重新创建的形式存在呢?

答案是当然可以,只需要在类上添加注解@Scope("prototype")即可,这样每次请求调用的类都是重新生成的(每次生成会影响效率

 

还有其他方法么?

答案是肯定的!

使用ThreadLocal 来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来


个人理解:

    1.violate是让工作线程的变量变化对于主线程和其他线程可见;而threadLocal则是让单例的共享变量在每次请求中不受其他请求的变量变化的影响。

    2.servlet是每次请求都会创建一个thread,每个thread中的变量都是相对隔离的,不受其他工作线程中变量的变化的影响(相当于多例多线程),在多线程请求时会发生线程安全问题,需要用到violate关键字;controller是单例,所有的请求都共享变量,一个请求将变量修改,其他的请求都会同步的看到,想要将各个请求的变量隔离,则需要用Threadlocal将类变量可见性的范围限制在本线程可见。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页