为什么局部变量线程安全
为什么局部变量线程安全
我们知道方法内部定义的变量属于局部变量,而局部变量的作用域仅仅存在一个方法的内部,不能被外部所引用,那这到底是为什么呢?
场景引入
假如存在方法计算斐波那契数列,什么是斐波那契数列呢,就是第一项和第二项都是1,从第三项开始,每一项都是前两项的和形如:1、1、2、3、5、8、13…那么多线程下变量r是否存在线程安全的问题呢?
public class Test {
public int[] fibonacci(int n){
int[] r = new int[n];
r[0] = r[1] = 1;
for (int i = 2; i <n ; i++) {
r[i] = r[i-1] + r[i-2];
}
return r;
}
public static void main(String[] args) {
Test test = new Test();
int[] fibonacci = test.fibonacci(5);
System.out.println(Arrays.toString(fibonacci));
}
}
其实仔细分析可以看到每调用一次fibonacci方法就会创建一个r对象,本质上有多个线程访问就会创建多个r对象,所以局部变量r应当不会存在数据竞争的问题,但是怎么系统分析呢?
这时候就要从编译原理入手了。
方法怎么运行
程序运行就是将JAVA代码翻译成CPU指令,并且执行CPU指令的过程,方法的调用亦是如此,如下方法调用为例
int a = 7;
int[] b = fibonacci(a);
int[] c = b;
当程序调用fibonacci方法时,首先要找到该方法的目的地址,跳转到该地址执行方法,最后CPU执行完方法准备返回时需要知道下一条指令对应的地址,如int[] c = b的地址,知道该地址后才能跳转到目的地址执行指令,跳转过程如下所示。
方法调用过程掌握较为简单,但是CPU如何获取方法的参数和返回地址呢?答案是CPU的堆栈寄存器,栈是一种线性结构就像手枪的弹夹,先进后出为栈。方法的调用就是一个压栈的过程,如下所示
存在三个方法A,B,C,其中方法A调用方法B,方法B调用方法C,每个方法入栈都会分配一片空间,这个空间称之为栈帧,栈帧中包含方法参数以及返回地址等内容,当调用方法时会创建栈帧同时入栈,调用方法完毕会从调用栈中弹出出栈,简单说就是方法和栈帧是同生共死的。
方法局部变量存放位置
现在知道了方法和栈帧是同生共死的,而局部变量的生命周期就是在方法内部,是否说明局部变量就是存储在栈帧内部呢?确实如此栈帧的内部就是包含布局变量的,方法调用栈图改进如下。
所以说局部变量是和栈帧同生共死的,如果想要跨越多个方法那么只能将变量创建在堆中,让方法共享。
调用栈和线程
调用栈和线程又有什么关系呢?每个线程都有自己的调用栈互不干扰,如下所示。
从这里看就知道了为什么局部变量线程安全,因为没有共享就没有伤害,局部变量对于每个方法的调用都是有独立的,线程与线程之前的调用栈互不干扰完全独立,这就是原因。
线程封闭
局部变量因为不会和其它线程共享变量,所以线程安全,这种思想能够很好的解决线程安全的问题,这种思想称之为线程封闭,官方解释是仅在单线程内访问数据。
采用线程封闭的案例常用于数据库连接的获取,采用的就是ThreadLock思想线程封闭每个线程都有独立的数据副本,线程安全效率高。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~