2021-12-02
java开发基础,java游戏开发,java基础教程,javascript基础教程
蓝blue
用多线程开发的人都知道,在多线程的开发过程中有可能会出现线程安全问题(专业术语叫内存可见性问题),但并不一定每次都会出现。出现这样的情况,也会另开发者头皮发麻,无从下手,接下来我们会慢慢深入,揭开多线程的神秘面纱。
本文主要介绍了Java多线程开发的优势,使用该技术可能会出现的一些内存不可见问题以及相应的解决措施。通过本文,读者将学习到如下几块知识:
下面进入正文
线程是Java语言中不可或缺的重要部分,它们能使复杂的异步代码变得简单,简化复杂系统的开发;能充分发挥多处理器系统的强大计算能力。
产生内存不可见的条件有俩个
1. 多个线程
2. 存在共享变量
当多个线程操作了共享变量时,就有可能会产生线程安全问题(内存不可见问题)。
产生内存不可见的问题有三个原因
1. 没有保证代码的原子性
2. 没有保证代码的可见性
3. 没有保证代码的有序性
1.1 没有保证原子性产生内存不可见的究极原因 ?
由于现代的CPU是多核的,可以实现并行,所以读书的时候我一直带着这样的疑问?
多核会不会同时操作同一内存下的数据:举例 代码 i++;多核处理器会不会同时执行这行代码?答案是不会同时执行。
请参考
1.2 没有保证代码的可见性产生内存不可见的究极原因?
可见性定义:一个线程的写对另一个线程立即可知。
现代的处理器都有读写缓冲区,读缓冲区异步从主存中读取数据,写缓冲区临时保存向内存写入的数据。有了写缓冲区可以保证指令流水线持续进行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时以异步的方式刷新写缓冲区,以及合并写缓冲区中对同一个地址的多次写,减少对内存总线的占用。
假设线程A,B,分别在俩个处理器上执行,初始时a=0 ,线程A先执行 a=1, 线程B 再执行 a=2,线程3去读变量a,最后a会是几呢? 答案 0 1 2 都有可能。
产生0的原因:线程A先执行 a=1,然后放到写缓冲区, 线程B 再执行 a=2,然后又放到写缓冲区,假设俩个写缓冲区还没又刷新的时候 线程3去读变量a,此时线程3读取到变量a就为0 。
产生1的原因:线程A先执行 a=1,然后放到写缓冲区, 线程B 再执行 a=2,然后又放到写缓冲区,假设线程A所在处理器的写缓冲区已刷新,线程B还没又刷新的时候, 线程3去读变量a,此时线程3读取到变量a就为1 。
产生2的原因:线程A先执行 a=1,然后放到写缓冲区, 线程B 再执行 a=2,然后又放到写缓冲区,假设线程B所在处理器的写缓冲区已刷新,线程A还没又刷新的时候, 线程3去读变量a,此时线程3读取到变量a就为2 。
综上所诉,没有保证代码的可见性产生内存不可见的究极原因是:写缓冲区的存在,且以异步的方式刷新缓冲。
1.3 没有保证代码的有序性产生内存不可见的究极原因?
编译器和处理器为了优化程序的性能会进行从排序,所以有序性会产生内存不可见问题。
Java虚拟机提供了Java内存模型来分析会不会产生内存可见性问题,Java内存模型也提供了一系列规则(happens-before等等)来辅助程序员更好的分析会不会产生内存可见行问题。
产生线程安全问题的原因有三个,解决方式无非就是 1.保证代码的原子性 2.保证代码的可见性 3.保证代码的有序性。
JVM 提供了synchronized 和volatile 关键字来解决问线程安全问题
Synchronized 可以保证代码的原子性、保证代码的可见性、保证代码的有序性。
volatile 可以保证代码的可见性、保证代码的有序性但不能保证代码的原子性。
所以Synchronized可以完全解决线程安全问题,而volatile不可以。
为什么本篇要这么写呢?因为读完Java并发编程书籍之后,我觉得非常的混乱,读完之后感觉没有收获,就想写点东西做个总结,可是又无从下手,不知道如何引入到Java内存模型和synchronized 、volatile 关键字。为什么无从下手呢,后来发现是对Java内存模型和synchronized 、volatile 理解不够到位。
Java内存模型可以帮助我们理解我们的代码会不会存在内存可见性问题。
synchronized 、volatile可以帮助我们解决内存可见性问题。前者是分析会不会产生问题,后者是产生问题之后的解决。
既然理清了俩者的区别,那么该如何过渡到这呢,LZ想从本篇引入到并发学习中。
10秒发布需求
快速获取报价及方案