当一个类被Spring管理并标注为@Component时,默认情况下它是一个单例(singleton),这意味着在整个应用生命周期中,Spring只会创建一个实例,尽管只有一个实例,但该实例的方法可以被多个线程同时调用。
如果你觉得这个问题很弱智,那么,你认为的…是对的。
一、线程的和共享的
首先,了解是什么是共享的,方法栈帧是线程的,方法代码也就是程序代码段是共享的。
方法栈帧:
- 每个线程都有自己的执行栈(stack)。
- 当一个线程调用一个方法时,会在自己的栈上创建一个新的栈帧(stack frame)来保存方法的局部变量、参数和返回地址等信息。
- 不同线程的栈帧是相互独立的。
方法代码:
- 方法的代码本身是存储在程序的代码段(code segment)中,这是共享的。
- 不同线程调用同一个方法时,它们共享相同的代码段,但每个线程有自己的栈帧。
二、方法栈帧和方法代码在哪
方法栈帧存储在栈中,方法代码也就是代码段,存储在方法区里
方法调用的内存布局
方法区(Method Area):
- 存储类的信息、常量、静态变量、即时编译后的代码等
- 代码段(Code Segment):
- 存储方法的指令代码。
- 这部分代码是共享的,所有线程都访问同一段代码。
栈(Stack):
- 线程私有,每个线程有自己的栈。
- 栈中保存方法的局部变量、参数、返回地址等信息。
堆(Heap):
- 用于动态分配的对象存储。
- 共享或私有的对象都可以存储在这里。
三、代码段为什么可以共享
只读属性:
- 代码段通常被标记为只读(read-only),这意味着任何线程都不能修改其中的内容。
- 由于代码段是不可变的,因此多个线程可以安全地共享同一段代码。
指令序列:
- 代码段包含的是指令序列,这些指令定义了程序的行为。
- 每个线程在执行这些指令时,实际上是按照顺序读取并执行指令,而不是修改它们。
CPU 指令执行:
- CPU 在执行指令时,是从内存中读取指令并执行。
- 由于指令是只读的,多个线程可以同时从同一位置读取指令,而不会影响彼此。
内存管理:
- 操作系统负责管理进程和线程的内存。
- 它会确保每个线程有自己的执行上下文(例如栈和寄存器状态),但可以让它们共享相同的代码段。
四、同一时间,大量调用同一个方法会导致性能问题
- 高并发调用会导致频繁的上下文切换。每次上下文切换都需要保存和恢复线程状态,这会消耗额外的时间
- 每个线程在调用方法时都会创建栈帧。如果调用次数过多,可能会耗尽栈空间,导致栈溢出
- 即使是并发执行,CPU 的处理能力仍然是有限的。如果线程数量超过 CPU 核心数,多余的线程只能等待调度,进一步增加延迟