龙空技术网

11|Java线程(下):为什么局部变量是线程安全的?

架构修炼师 397

前言:

此刻看官们对“java线程安全的数据结构”大致比较着重,咱们都想要知道一些“java线程安全的数据结构”的相关内容。那么小编同时在网上网罗了一些关于“java线程安全的数据结构””的相关资讯,希望看官们能喜欢,大家一起来了解一下吧!

当多个线程访问共享变量时,导致并发问题,Java里面是不是所有的变量都是共享变量呢?不少同学给所有的局部变量设置同步,显然没有把共享变量说清楚,那么Java局部变量是否存在并发问题呢?

很多人知道,局部变量是不存在竞争的,至于原因吗?说不清楚。

你需要稍微知道点编译原理的东西,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支持一种栈结构,先入后出。因为 这个是跟方法调用相关的,所以叫做调用栈。

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

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

局部变量存哪里?

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

局部变量的作用域是方法内部,也就是说当方法执行完,局部变量就没用了,局部变量应该跟方法共生死,所以局部变量放到调用栈那边是相当合理,事实上,的确这样,局部变量就是放到了调用栈里,于是调用栈结构就变成了如下:

基本都会知道,所有的教材都会告诉你,new出来的对象存在堆里,只不过很多人不清楚 堆跟栈的区别,以及为什么要区分 堆跟栈,现在清楚了局部变量是跟方法同生死的,一个变量想跨越方法的边界,就必须在堆里。

调用栈与线程

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

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

线程封闭

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

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

采用线程封闭的案例很多,数据库连接池获取的 connection,jdbc规范并没有要求这个connection必须是线程安全的。数据库连接池通过线程封闭技术,获取到的connection在释放之前不会再分配给其他请求。从而保证不会有并发问题。

标签: #java线程安全的数据结构