什么?你居然不知道Spring为什么要设计成单例?

面试官:你知道spring默认是单例,还是多例的?

我:好像是单例的。。

面试官:为什么spring设计成单例,也顺便讲讲spring是不是单例中线程安全问题?

我:单例我到知道,线程安全是啥。。。 还是回家吧。

很多人都知道spring是单例的,也知道单例模式,但是融合到一起却经常讲不清楚为什么spring是单例的,这么设计的好处,下面就给大家分享一下我的几点总结。

首先我们来看看单例和多例的优缺点:

单例

优点 缺点
1 减少请求时候创建对象的开销,提升性能 2 减少jvm垃圾回收 1 对于有状态的变量可能会造成线程安全问题,因为只有一个实例,如果操作的是有状态的全局变量,多个线程之间可能会操作同一个变量和对象导致线程不安全问题

多例

优点 缺点
1 线程安全,每个请求过来都分配一个新的对象,里面的所有东西属性方法都是该线程独享的 1 资源开销大,创建对象需要消耗性能 2 会产生大量的垃圾对象

Spring框架中的bean 或者说组件,默认是单例的。
单例模式确保了某个类只有一个实例,并且自行实例化,向整个系统提供这个实例。主要的优点就是减少对象的创建开销;减少jvm垃圾回收,因为单例是静态变量不会回收;
在多线程的情况下,Web容器会向每个请求分配一个线程。这些线程会执行对应的业务逻辑。如果在执行的时候对单例对象进行了修改,则必须考虑到线程同步的问题,所以一般我们在单例的对象中使用成员变量,就需要考虑在多线程中,有可能两个线程操作的成员变量是一个,这样就可能会造成线程安全问题;

举个例子有个类A,里面有个变量num,初始化为0,两个线程都访问spring的类A,分别对num + 1;
可能大家会写出以下的伪代码
image.png
这就是单例中新手程序员经常会犯的低级错误,那么spring又是有什么解决方案呢?
显然第一种就是变成多例模式,但是开销过大;那么第二种解决方案就是ThreadLocal对象了;

同步机制
ThreadLocal 和 线程同步机制
  线程同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题。
  ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

在spring 中是使用 ThreadLocal 解决线程安全问题
线程安全问题主要是全局变量和静态变量引起的。
若每个线程中对全局变量、静态变量读操作,而无写操作,一般来说这个全局变量是线程安全的。
若多个线程同时执行写操作,需要考虑线程同步问题,否则影响线程安全。
spring 使用ThreadLocal 实现高并发下 共享资源的同步。

原理:
  为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线
程都完全拥有该变量。【每个线程其实是改变的是自己线程的副本,而不是真正要改变的变量,所以效果就是每个线程都有自己的,“这其实就将共享变相为人人有份!”】

ThreadLocal 如何实现为每一个变量维护变量的副本。

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%