为啥要有多线程编程(多线程学习笔记)
出现多线程编程的历史原因:(为了提高速度和性能,合理利用CPU)
我们的 CPU、内存、I/O 设备都在不断迭代,不断朝着更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度差异。
始终是CPU > 内存 > IO设备
为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了相应贡献。
主要体现为:
CPU 增加了缓存,以均衡与内存的速度差异;
操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;
编译程序优化指令执行次序,使得缓存(CPU缓存)能够得到更加合理地利用。
并发程序很多诡异问题的根源也在这里。
源头之一,CPU缓存导致的可见性问题(多核时代,每个CPU都有自己的缓存,这计算机体系结构)
一个线程对共享变量的修改,另外一个线程能够立刻看到,我们成为可见性。(比如:我看见常威打来福,而不仅仅是看到了受伤的来福)
源头之二,线程切换带来的原子性问题(这属于操作系统)
操作系统做任务切换,可以发生在任何一条 CPU 指令执行完,是的,是 CPU 指令,而不是高级语言里的一条语句。
源头三,编译优化带来的有序性问题(编译器软件)
顾名思义,有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序。
为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:
1、CPU 增加了缓存,以均衡与内存的速度差异;
2、操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;
3、编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。
个人感悟:其实从这三点就可以看出,我们平常的编程语言编程是表面上的东西,而里面是另外一种情况(操作系统、CPU以及你不接触的编译器软件),多线程编程技术就是为了平衡这个“表面”与“里面”的一个差异,多线程编程技术其实是不得已的一个填坑方案和技术手段而已。(目的还是满足合理利用CPU这么快速的资源)
各个编程语言都会遇到上面谈到的问题。每个编程语言应对的方式也不一样。
在Java这门编程语言中,有自己独特的办法。
那么如何解决这三个问题呢?
既然你CPU增加了缓存,这样导致了可见性问题,那我不用CPU的缓存不就可以了?
禁用CPU的缓存,直接从内存中读取或写入,内存是所有CPU共享的,所以这从根本上就解决了可见性问题。但是内存的速度是慢于CPU的,这样性能就会有所下降。
那如何在代码语句上体现出来禁止使用CPU缓存,那么不管是哪个线程在哪个CPU上运行这段代码时,都被限制了使用CPU的缓存,直接从内存中读取或写入,彼此之间都清楚动了什么地方。
我们知道编译软件优化了指令的执行次序,使得CPU缓存能够更加合理地利用。
但是上面已经禁用了CPU缓存,尽然CPU缓存不让用了,那编译优化的意义也就不复存在了,所以编译优化也要禁用掉,这样可见性和有序性就都得到解决了,只是这样的话我们的程序性能就真的大大降低了,唯一的办法就是按需禁用,不能一直禁用,不然程序就真的太慢了。
Java提出的独特的办法就是内存模型,Java的内存模型规范了JVM按需禁用CPU缓存和编译优化的办法。在我们编程上的体现为:volatile、synchronized和final三个关键字。还有就是JVM的6项Happens-Before规则。
上面把可见性和有序性的问题给解决了,还有线程切换带来的原子性问题呢?
同样的思路,也是按需禁用线程切换,具体在Java代码上的体现就是当线程执行到一段代码(类、方法、代码块)时,为这段代码加上锁,其他线程别想进来,这样就达到了切换线程切换不成的目的,得等当前线程出去了,其他线程拿到锁才可以进入这段代码执行对应逻辑。
目的:为了合理利用CPU这个快速的资源。(CPU快嘛,而且现在都是多核了,这是肯定会面对并且要解决的问题)
多线程编程:为了达到上面的目的,以及CPU、操作系统、编译器三者的优化所带来的问题,不得已的一种填坑技术。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
