我们都知道多线程同时访问共享变量存在并发问题,那么是不是所有的共享变量都存在并发问题,那Java中的局部变量是否存在并发问题?
比如,下面代码里的 fibonacci() 这个方法,会根据传入的参数n,返回1到n的斐波拉契数列,斐波拉契数列类似这样:1、1、2、3、5、8、13、21....第一项和第二项是1,从第三项开始,每一项等于前两项之和.在这个方法里有个局部变量:数组r用来保存数列的结果,每次计算完一项,都会更新数组r的对应位置中的值.如果多个线程调用fibonacci()这个方法,数组r是否存在数据竞争
// 返回斐波那契数列
int[] fibonacci(int n) {
// 创建结果数组
int[] r = new int[n];
// 初始化第一、第二个数
r[0] = r[1] = 1; // ①
// 计算 2..n
for(int i = 2; i < n; i++) {
r[i] = r[i-2] + r[i-1];
}
return r;
}
假设多个线程执行到1⃣️初,多个线程都要对数组r的第一项和第二项赋值,感觉存在数据竞争了,在项目中也遇到这样的情况,在一个方法中共用了一个局部变量,出现了并发的问题,怀疑是这里出现了问题,最后经同事讲解,发现不是这里,局部变量不存在多线程问题.但是至于为什么局部变量不存在多线程问题却不知道原因,以下将从几个方面讲解.
#方法是如何被执行的 高级语言的普通语句,例如上面的r[i] = r[i-2] + r[i-1];翻译成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支持一种栈结构,因为这个栈和方法的调用相关,也称为调用栈.
例如有三个方法A,B,C,它们的调用关系是A->B->C(A调用B,B调用C),在运行时,会构建出下面的调用栈, 每个方法在调用栈里都有自己的独立空间,称为栈桢,每个栈帧里都有对应方法需要的参数和返回地址。 当调用方法时,会创建新的栈桢,并压入栈.当方法返回时,对应的栈桢就会被自动弹出,也就是说,栈桢和方法是同生共死的
利用栈结构支持方法调用这个方式非常普遍,以至于cpu里内置了栈寄存器.虽然各家编程语言定义的千奇百怪,当方法内部的执行原理却是一致的:都是靠栈结构解决的
局部变量存在哪里
局部变量的作用是方法内部,也就是说方法执行完,局部变量就没有用了,局部变量应该和方法同生共死,所以局部变量放到调用栈里那儿是相当的合理,局部变量就是放到了调用栈里, 调用栈的结构:
调用栈和线程
两个线程可以同时用不同的参数调用相同的方法,每个线程都有自己独立的调用栈.如果不是这样两个线程就会互相干扰了,如下图所示,线程A,B,C都有自己独立的调用栈.
线程封闭
方法里的局部变量,因为不会和其他线程共享,所以没有并发问题-线程封闭 仅在单线程内访问数据,由于不存在共享,所以即便不同步也不会有并发问题,性能杠杠的。
采用线程封闭的案例非常多,例如从数据库连接池中获取Connection,在 JDBC 规范里并没有要求这个 Connection必须是线程安全的.数据库连接池通过线程封闭技术,保证一个Connection一旦被一个线程获取后,在线程关闭Connection之前的这段时间里,不会再分配给其他线程,从而保证了Connection不会有线程问题.