局部变量为什么是线程安全的

我们一遍一遍重复讲到,多个线程同时访问共享变量时,会导致并发问题,那在Java 语言里,是不是所有的变量都是共享变量呢?工作中我发现不少同学会给方法里的局部变量设置同步,显然这些同学并没有把共享变量说清楚。那么Java 方法里的局部变量是否存在并发问题呢?

其实很多人都知道局部变量是不存在数据竞争的,至于原因嘛,就说不清楚了。

那它背后的原因到底是什么样的呢?要弄清楚这个,你需要一点编译原理的知识,你知道在CPU 层面,是没有方法概念的。CPU 的眼里只有一条条的指令。编译程序,负责把高级语言里的方法转换成一条条的指令。所以你可以站在编译器实现的角度来思考,怎么完成方法到指令的转换?

方法是如何被执行的

高级语言的普通语句里,翻译成 CPU 的指令相对简单,可方法的调用就比较复杂了。例如下面这三行代码:第 1 行,声明一个 int 变量 a;第 2 行,调用方法 fibonacci(a);第 3 行,将 b 赋值给 c。

int a = 7;
int[] b = fibonacci(a);
int[] c = b;

当你调用 fibonacci(a) 的时候,CPU 要先找到方法 fibonacci() 的地址, 然后跳转到这个地址去执行代码,最后 CPU 执行完方法 fibonacci() 之后,要能够返回。 首先找到调用方法的下一条语句地址,:也就是int[] c=b;的地址,再跳转到这个地址去执行。 你可以参考下面这个图再加深一下理解。

                

到这里,方法调用的过程你就清楚了,但还有一个重要问题,“CPU 去哪里找到调用方法的参数和返回地址?” 如果你熟悉CPU的工作原理,你应该立刻会想到:通过CPU的堆栈寄存器。CPU 支持一种栈结构,先入后出。因为这个栈是和方法调用相关的,所以叫做调用栈。 

例如,有三个方法 A、B、C,他们的调用关系是 A->B->C(A 调用 B,B 调用 C),在运行时,会构建出下面这样的调用栈。每个方法在调用栈里都有自己的独立空间,称为栈帧,每个栈帧里都有对应方法需要的参数和返回地址。当调用方法时,会创建新的栈帧,并压入调用栈;当方法返回时,对应的栈帧就会被自动弹出。也就是说,栈帧和方法是同生共死的。

                            

 利用栈结构来支持方法调用这个方案非常普遍,以至于 CPU 里内置了栈寄存器。虽然各家编程语言定义的方法千奇百怪,但是方法的内部执行原理却是出奇的一致:都是靠栈结构解决的。Java 语言虽然是靠虚拟机解释执行的,但是方法的调用也是利用栈结构解决的。

局部变量存哪里?

我们已经知道方法的调用在CPU 眼里是怎么执行的。但还有一个关键问题:方法内的局部变量存哪里?

局部变量的作用域是方法内部,也就是说当方法执行完,局部变量就没用了。局部变量应该和方法共生死。此时你应该会想到调用栈的栈帧,调用栈的栈帧就是和方法同生共死的,所以局部变量放到调用栈里那儿是相当的合理。事实上,的确是这样的,局部变量就是放到了调用栈里。于是调用栈的结构就变成了下图这样。

                          

这个结论相信很多人都知道,因为学 Java 语言的时候,基本所有的教材都会告诉你 new 出来的对象是在堆里,局部变量是在栈里,只不过很多人并不清楚堆和栈的区别,以及为什么要区分堆和栈。现在你应该很清楚了,局部变量是和方法同生共死的,一个变量如果想跨越方法的边界,就必须创建在堆里。

调用栈与线程

两个线程可以同时用不同的参数调用相同的方法。那调用栈跟线程是一种什么关系呢?答案:每个线程都有自己独立的调用栈。如果不是这样,那么线程就相互干扰了,如下图所示:

                              

现在,让我们回过头来再看篇首的问题:Java 方法里面的局部变量是否存在并发问题?现在你应该很清楚了,一点问题都没有。因为每个线程都有自己的调用栈,局部变量保存在线程各自的调用栈里面,不会共享,所以自然也就没有并发问题。再次重申一遍:没有共享,就没有伤害

线程封闭

方法里的局部变量,因为不会和其他线程共享,所以没有并发问题,这个思路很好,已经称为解决并发问题的一种途径。同时还有个响亮的名字叫做 线程封闭

即 仅在单线程内访问数据。由于不存在共享,所以即便不同步也不会出现并发问题。

采用线程封闭技术的案例非常多,例如从数据库连接池里面获取的Connection ,在 JDBC 规范里并没有要求这个 Connection 必须是线程安全的。数据库连接池通过线程封闭技术,保证一个 Connection 一旦被一个线程获取之后,在这个线程关闭 Connection 之前的这段时间里,不会再分配给其他线程,从而保证了 Connection 不会有并发问题。

总结

调用栈是一个通用的计算机概念,所有的编程语言都会涉及。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值