为什么局部变量线程安全

为什么局部变量线程安全

我们知道方法内部定义的变量属于局部变量,而局部变量的作用域仅仅存在一个方法的内部,不能被外部所引用,那这到底是为什么呢?

场景引入

假如存在方法计算斐波那契数列,什么是斐波那契数列呢,就是第一项和第二项都是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的地址,知道该地址后才能跳转到目的地址执行指令,跳转过程如下所示。

image-20220221233644254

方法调用过程掌握较为简单,但是CPU如何获取方法的参数和返回地址呢?答案是CPU的堆栈寄存器,栈是一种线性结构就像手枪的弹夹,先进后出为栈。方法的调用就是一个压栈的过程,如下所示

存在三个方法A,B,C,其中方法A调用方法B,方法B调用方法C,每个方法入栈都会分配一片空间,这个空间称之为栈帧,栈帧中包含方法参数以及返回地址等内容,当调用方法时会创建栈帧同时入栈,调用方法完毕会从调用栈中弹出出栈,简单说就是方法和栈帧是同生共死的。

image-20220221235212670

方法局部变量存放位置

现在知道了方法和栈帧是同生共死的,而局部变量的生命周期就是在方法内部,是否说明局部变量就是存储在栈帧内部呢?确实如此栈帧的内部就是包含布局变量的,方法调用栈图改进如下。

image-20220221235830438

所以说局部变量是和栈帧同生共死的,如果想要跨越多个方法那么只能将变量创建在堆中,让方法共享。

调用栈和线程

调用栈和线程又有什么关系呢?每个线程都有自己的调用栈互不干扰,如下所示。

image-20220222000413792

从这里看就知道了为什么局部变量线程安全,因为没有共享就没有伤害,局部变量对于每个方法的调用都是有独立的,线程与线程之前的调用栈互不干扰完全独立,这就是原因。

线程封闭

局部变量因为不会和其它线程共享变量,所以线程安全,这种思想能够很好的解决线程安全的问题,这种思想称之为线程封闭,官方解释是仅在单线程内访问数据

采用线程封闭的案例常用于数据库连接的获取,采用的就是ThreadLock思想线程封闭每个线程都有独立的数据副本,线程安全效率高。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值