为什么局部变量线程安全

为什么局部变量线程安全

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

场景引入

假如存在方法计算斐波那契数列,什么是斐波那契数列呢,就是第一项和第二项都是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思想线程封闭每个线程都有独立的数据副本,线程安全效率高。

\

Guess you like

Origin juejin.im/post/7067196887111041054