概述
概述
我们知道Spring IoC 容器中只会存在一个 bean 的实例,无论一次调用还是多次调用,始终指向的都是同一个 bean 对象。
对于单实例来说,所有线程都共享同一个 bean 实例,自然就会发生资源的争抢,从而导致线程不安全。
举例:
新增服务类ThreadUnSafeService
@Service
public class ThreadUnSafeService {
public int i;
public void add() {
i++;
}
public void sub() {
i--;
}
public int getValue() {
return i;
}
}
启动十个线程,每个线程各做一千次加减:
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "applicationContext.xml" });
for (int j = 0; j < 10; j++) {
new Thread(new Runnable() {
@Override
public void run() {
ThreadUnSafeService service = (ThreadUnSafeService) context.getBean("threadUnSafeService");
for (int i = 0; i < 1000; i++) {
service.add();
}
for (int i = 0; i < 1000; i++) {
service.sub();
}
System.out.println(Thread.currentThread().getName() + "-" + service.getValue());
}
}).start();
}
}
}
输出结果:
Thread-2-0
Thread-3-0
Thread-8-1201
Thread-10-663
Thread-1-1443
Thread-9-1380
Thread-7-1846
Thread-6-488
Thread-4-2507
Thread-5-2856
从结果可以看出,运行结果都不是 0,这明显的是线程不安全!!
因为 10 个线程获取的 ThreadUnSafe 实例都是同一个,并且 10 个线程都对同一个资源 i 发生了争抢,所以才会导致线程安全问题的发生。
解决方案一:scope 的值改为 prototype
@Service
@Scope("prototype")
public class ThreadSafeService {
public int i;
public void add() {
i++;
}
public void sub() {
i--;
}
public int getValue() {
return i;
}
}
输出结果:
Thread-3-0
Thread-1-0
Thread-7-0
Thread-2-0
Thread-4-0
Thread-6-0
Thread-5-0
Thread-8-0
Thread-9-0
Thread-10-0
prototype 作用域下,每次获取的 ThreadUnSafe 实例都不同,所以自然不会有线程安全的问题。
解决方案二:将bean改为无状态的
无状态bean就是没有共享变量,所以就不会产生线程安全问题。
@Service
public class ThreadSafeService {
public void getValue() {
int val = 0;
for (int i = 0; i < 1000; i++) {
val++;
}
for (int i = 0; i < 1000; i++) {
val--;
}
System.out.println(val);
}
}
调用:
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "applicationContext.xml" });
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
ThreadSafeService service = (ThreadSafeService) context.getBean("threadSafeService");
service.getValue();
}
}).start();
}
}
}
输出结果:
0
0
0
0
0
0
0
0
0
0
解决方案三:加锁
既然是线程安全问题,那就加锁。
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"applicationContext.xml"});
for (int j = 0; j < 10; j++) {
new Thread(new Runnable() {
@Override
public void run() {
ThreadUnSafeService service = (ThreadUnSafeService) context.getBean("threadUnSafeService");
synchronized (service) {
for (int i = 0; i < 1000; i++) {
service.add();
}
for (int i = 0; i < 1000; i++) {
service.sub();
}
System.out.println(Thread.currentThread().getName() + "-" + service.getValue());
}
}
}).start();
}
}
}
运行结果为 0。
毫无疑问加锁确实可以,但是加锁会增加性能上的开销。
解决方案四:ThreadLocal
ThreadLocal 在自己线程内创建一个变量的副本,规避了线程安全问题。
@Service
public class ThreadUnSafeService {
public static ThreadLocal<Integer> i = new ThreadLocal<>();
public void add() {
Integer integer = i.get();
if (null == integer){
integer = 0;
}
integer++;
i.set(integer);
}
public void sub() {
Integer integer = i.get();
if (null == integer){
integer = 0;
}
integer--;
i.set(integer);
}
public int getValue() {
return i.get();
}
}
运行结果为 0。
但是ThreadLocal为每个线程创建变量的副本,带来了空间上的开销。
总之,解决线程安全问题思路分为两种。
第一种,消除共享变量,是线程不安全问题不存在,方法有将bean改为无状态的、scope 的值改为 prototype、ThreadLocal。
第二种,就是加锁。
Spring自身方案
无状态的bean
在Spring中,绝大部分bean都是无状态的,因此即使这些bean默认是单例的,也不会出现线程安全问题的。比如controller、service、dao这些类,这些类里面通常不会含有成员变量,因此它们被设计成单例的。如果这些类中定义了实例变量,就线程不安全了,所以尽量避免定义实例变量。
有状态的bean
Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象” 采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9-2所示。
这样用户就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定的。
最后
以上就是怕黑小鸽子为你收集整理的Spring的线程安全问题概述解决方案一:scope 的值改为 prototype解决方案二:将bean改为无状态的解决方案三:加锁解决方案四:ThreadLocalSpring自身方案的全部内容,希望文章能够帮你解决Spring的线程安全问题概述解决方案一:scope 的值改为 prototype解决方案二:将bean改为无状态的解决方案三:加锁解决方案四:ThreadLocalSpring自身方案所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复