Java并发编程实战
王宝令
资深架构师
立即订阅
15151 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

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

王宝令 2019-03-23
我们一遍一遍重复再重复地讲到,多个线程同时访问共享变量的时候,会导致并发问题。那在 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(63)

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

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

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

    作者回复: 是的

    2019-03-25
    2
    18
  • 西西弗与卡夫卡
    因为调用方法时局部变量会进线程的栈帧,线程的栈内存是有限的,而递归没控制好容易造成太多层次调用,最终栈溢出。

    解决思路一是开源节流,即减少多余的局部变量或扩大栈内存大小设置,减少调用层次涉及具体业务逻辑,优化空间有限;二是改弦更张,即想办法消除递归,比如说能否改造成尾递归(Java会优化掉尾递归)

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

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

    作者回复: 全面!

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

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

    2019-03-27
    3
    7
  • ack
    如果是jvm栈空间太小了导致的栈溢出,可以通过-Xss增大栈空间大小。并且递归方法一定要有终止的return条件
    2019-03-23
    7
  • Geek_cc0a3b
    new 出来的对象是在堆里,局部变量是在栈里,那方法中new出来的对象属于局部变量,是保存在堆里还是在栈里呢?

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

    2019-03-24
    6
  • crudBoy
    其实就是并发调用方法而产生的局部变量指向的内存地址都是不同的。 所以同一时刻只会有一个线程去操作这些局部变量指向的内存。
    2019-05-09
    3
  • 老师我有个问题:有一个方法的参数是引用类型,方法内定义了一个局部变量,在方法内将引用类型的参数赋值给该局部变量,然后再操作该局部变量会不会存在线程安全问题?比如:
    void test(account a){
        account b = a;
        b.addMoney(100);
    }

    作者回复: 存在

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

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

    2019-03-23
    3
  • 邢宇超
    CPU 的堆栈寄存器和栈帧什么关系 老师 求你回答一下我一个问题吧 我的问题你都没回答过

    作者回复: 堆栈寄存器指向栈顶内存地址

    2019-07-15
    2
  • 杨鹏程baci
    老师好,我理解一下,您绿的例子里面线程池获取一个线程在方法体里面赋给了一个局部变量,但是从线程池获取线程的过程还是要用到锁吧,只是后面这个线程也不会被重复分配了,所以不需要加锁
    2019-06-28
    2
  • 000
    每次递归就创建一个栈桢,没控制好结束,会导致栈空间溢出
    2019-07-03
    1
  • 非礼勿言-非礼勿听-非礼勿视
    递归可能导致栈溢出,基本上都知道原因,这里说下个人对于如何避免的浅见:
    1.递归基本上都可改写成循环方式
    2.限制递归层次
    3.其实和1是一个道理,模拟栈结构
    2019-03-30
    1
  • Tuberose
    还有一点就是 静态方法是否是线程安全的?j

    作者回复: 不安全

    2019-03-25
    1
  • 小美
    线程封闭:jdbcConnection 如何保证线程封闭这块方便老师具体讲解下吗~

    作者回复: 放到threadlocal里说这个事情吧

    2019-03-24
    1
  • QQ怪
    老师抛砖引玉的学习方法我觉得非常好,十分清楚的弄懂了本文全部内容,而且十分易懂。
    课后问题:递归调用太深会在创建过多的调用栈,也就是会创建过多栈帧,导致栈溢出,但是递归方法写起来简单,但会出现以上问题,解决办法可以改成非递归写法,自己手动维护个栈
    2019-03-23
    1
  • 文兄
    局部变量是线程安全的
    对于这句话我的理解是,在方法内创建的变量是线程安全的
    那么方法的入参,是否是线程安全的呢
    方法的入参是对象的情况下还是否是线程安全的呢

    希望有大手子答疑
    2019-12-06
  • 阿卡牛
    老师的讲解行云流水,重剑无锋,享受
    2019-11-12
  • 朕爱吾妃
    每一次递归到一个方法上时,就会在有一个入栈的操作,当方法执行结束,就会有出栈,当递归过多的时候,就会有很多入栈的操作而没有出栈的操作,造成栈容量越来越大,而栈内每一个变量引用都是需要占用内存的,也就说明栈是有大小的,所以,当超出了栈最大容量的时候,就会造成栈溢出的情况的,这就是原因所在。

    作者回复: 👍

    2019-11-11
收起评论
63
返回
顶部