Java 并发编程实战
王宝令
资深架构师
72485 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

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

解决方案
递归调用导致栈溢出的原因
研究原理性和通用的东西
推断和源码验证
不存在共享的并发问题
仅在单线程内访问数据
局部变量在各自的调用栈中
线程独立的调用栈
调用栈中的局部变量
局部变量和方法同生共死
栈结构支持方法调用
方法调用过程
CPU执行方法
方法内的局部变量
Java语言中的共享变量
多线程访问共享变量
课后思考
总结
线程封闭
调用栈与线程
局部变量存储位置
方法调用执行过程
变量共享问题

该思维导图由 AI 生成,仅供参考

我们一遍一遍重复再重复地讲到,多个线程同时访问共享变量的时候,会导致并发问题。那在 Java 语言里,是不是所有变量都是共享变量呢?工作中我发现不少同学会给方法里面的局部变量设置同步,显然这些同学并没有把共享变量搞清楚。那 Java 方法里面的局部变量是否存在并发问题呢?下面我们就先结合一个例子剖析下这个问题。
比如,下面代码里的 fibonacci() 这个方法,会根据传入的参数 n ,返回 1 到 n 的斐波那契数列,斐波那契数列类似这样: 1、1、2、3、5、8、13、21、34……第 1 项和第 2 项是 1,从第 3 项开始,每一项都等于前两项之和。在这个方法里面,有个局部变量:数组 r 用来保存数列的结果,每次计算完一项,都会更新数组 r 对应位置中的值。你可以思考这样一个问题,当多个线程调用 fibonacci() 这个方法的时候,数组 r 是否存在数据竞争(Data Race)呢?
// 返回斐波那契数列
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;
}
你自己可以在大脑里模拟一下多个线程调用 fibonacci() 方法的情景,假设多个线程执行到 ① 处,多个线程都要对数组 r 的第 1 项和第 2 项赋值,这里看上去感觉是存在数据竞争的,不过感觉再次欺骗了你。
其实很多人也是知道局部变量不存在数据竞争的,但是至于原因嘛,就说不清楚了。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Java方法里的局部变量是线程安全的,因为每个线程都有自己独立的调用栈,局部变量保存在各自的调用栈里,不会共享,所以不存在并发问题。这种思路成为解决并发问题的重要技术,称为线程封闭,即仅在单线程内访问数据。由于不存在共享,即便不同步也不会有并发问题,性能优越。这种技术在实际应用中非常广泛,例如数据库连接池通过线程封闭技术保证连接不会有并发问题。文章通过解释调用栈的结构和方法内的局部变量存储位置,阐述了局部变量线程安全的原因,强调了学习原理性和通用性知识的重要性。同时,提出了课后思考问题,引导读者深入思考。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(100)

  • 最新
  • 精选
  • uyong
    栈溢出原因: 因为每调用一个方法就会在栈上创建一个栈帧,方法调用结束后就会弹出该栈帧,而栈的大小不是无限的,所以递归调用次数过多的话就会导致栈溢出。而递归调用的特点是每递归一次,就要创建一个新的栈帧,而且还要保留之前的环境(栈帧),直到遇到结束条件。所以递归调用一定要明确好结束条件,不要出现死循环,而且要避免栈太深。 解决方法: 1. 简单粗暴,不要使用递归,使用循环替代。缺点:代码逻辑不够清晰; 2. 限制递归次数; 3. 使用尾递归,尾递归是指在方法返回时只调用自己本身,且不能包含表达式。编译器或解释器会把尾递归做优化,使递归方法不论调用多少次,都只占用一个栈帧,所以不会出现栈溢出。然鹅,Java没有尾递归优化。

    作者回复: 很全面了。所有的递归算法都可以用非递归算法实现,大学老师好像还出过这样一道题......

    2019-03-23
    15
    237
  • suynan
    对于这句话:“ new 出来的对象是在堆里,局部变量在栈里” 我觉得应该是对象在堆里,引用(句柄)在栈里

    作者回复: 是的

    2019-03-25
    12
    76
  • 西西弗与卡夫卡
    因为调用方法时局部变量会进线程的栈帧,线程的栈内存是有限的,而递归没控制好容易造成太多层次调用,最终栈溢出。 解决思路一是开源节流,即减少多余的局部变量或扩大栈内存大小设置,减少调用层次涉及具体业务逻辑,优化空间有限;二是改弦更张,即想办法消除递归,比如说能否改造成尾递归(Java会优化掉尾递归)

    作者回复: 没怎么听说Java尾递归优化的事情,不太确定。最好不要依赖这个

    2019-03-23
    27
  • Geek_cc0a3b
    new 出来的对象是在堆里,局部变量是在栈里,那方法中new出来的对象属于局部变量,是保存在堆里还是在栈里呢?

    作者回复: 对象堆里,但是指针在栈里

    2019-03-24
    9
    26
  • Xiao
    如果方法内部又有多线程,那方法内部的局部变量是不是也不是线程安全。

    作者回复: 你看看编译器允不允许这样做吧

    2019-03-27
    11
    19
  • bing
    当遇到递归时,可能出现栈空间不足,出现栈溢出,再申请资源扩大栈空间,如果空间还是不足会出现内存溢出oom。 合理的设置栈空间大小; 写递归方法注意判断层次; 能用递归的地方大多数能改写成非递归方式。

    作者回复: 全面!

    2019-03-23
    16
  • 老师我有个问题:有一个方法的参数是引用类型,方法内定义了一个局部变量,在方法内将引用类型的参数赋值给该局部变量,然后再操作该局部变量会不会存在线程安全问题?比如: void test(account a){ account b = a; b.addMoney(100); }

    作者回复: 存在

    2019-04-19
    7
    8
  • 卡西米
    请教一个问题JAVA的栈跟cpu的栈有什么关系?

    作者回复: 没关系,只是原理类似

    2019-03-23
    8
  • Thong2018
    看老师讲Java并发编程的知识,让我明白了很多之前在大学里没有学明白的知识点。要是大学的专业课本也能像老师这样讲得通俗易懂而又不失深度就好了,用“大道至简”来形容老师的授课风格再好不过了

    作者回复: 过奖了😄

    2019-08-26
    7
  • 文兄
    局部变量是线程安全的 对于这句话我的理解是,在方法内创建的变量是线程安全的 那么方法的入参,是否是线程安全的呢 方法的入参是对象的情况下还是否是线程安全的呢 希望有大手子答疑

    作者回复: 粗略的结论是,引用传递不安全,值传递安全

    2019-12-06
    2
    5
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部