Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。
(1)对于prototype(原型)作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
(2)对于singleton(单例)作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
注:
prototype原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个实例都有自己的属性和状态,而Singleton全局只有一个对象。推荐有状态的bean使用prototype作用域,而对无状态的bean使用singleton(单例)作用域。
也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
代码案例:
假设此时有一个User类,其中有一个Count变量,然后又两个线程对这一个bean进行操作。
public class UnsafeBean {
private int count;
public int getCount() {
return count;
}
public void increment() {
count++;
}
}
public class UnsafeThreadDemo {
public static void main(String[] args) {
final UnsafeBean bean = new UnsafeBean();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
bean.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + bean.getCount());
}
}
在这个示例中,两个线程同时对 UnsafeBean 对象进行 1000 次递增操作。由于 count 属性没有同步控制,多个线程同时访问 increment() 方法可能导致竞态条件,结果可能是不确定的。
所以就可以通过ThreadLocal来使得每个线程都有自己的ThreadLocal变量副本实例,那么避免了多个线程之间竞态而导致线程安全问题;
public class ThreadLocalBean {
private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);
public int getCount() {
return count.get();
}
public void increment() {
count.set(count.get() + 1);
}
}
public class ThreadLocalBean {
private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);
public int getCount() {
return count.get();
}
public void increment() {
count.set(count.get() + 1);
}
}
1409

被折叠的 条评论
为什么被折叠?



