01 线程基础知识
02_从一则招聘谈起
线程的发展历史、编程结构的发展历史就是对CPU性能不断压榨的历史。
注意学习方法,时间多就好好理解,真正理解透彻,做到见名知意
时间不多,直接背,而且老马也建议直接背,知道吗? 但是据我的面试经验,年限少的可行,年限多的不可行,直接问深入或者实操心得,一问就懵逼,还记得腾讯互金的二面吗? 问哨兵的使用细节,一问就露馅儿。
还有分布式锁的使用场景,redis的使用场景,这些东西八股文只能应对中小厂,大厂就不行了。
03_线程的历史-CPU性能压榨的血泪史
开局一张图,放这里
1、单进程人工切换
纸带机 : 大量时间等着人把纸带放到CPU里。
2、多进程批处理
多个任务批量执行
3、多进程并行处理
把程序写在不同的内存位置上 来回切换: 现代操作系统压榨CPU的常用思路。
4、多线程
一个程序内不同任务的来回切换、QQ里,有的线程在等待网络IO,有的在刷新UI,有的在执行数据持久化。
selector epoll、
线程也会和网络编程模型,或者IO结合在一起。 IO的知识是面试加分项,,可以去看周老师的课。
5、Java叫纤程、go叫协程
他们是绿色线程,是用户管理的(而不是OS管理的)线程。
04_进程线程纤程区别
05_底层理解进程
记住上图,很经典。
比如QQ.exe文件是Windows下可运行的程序,它是磁盘里的静态文件,双击它就开始运行,操作系统找到这个可执行文件 把相关的信息load到内存,再双击一下QQ,内存里又load一份,内存里每一份QQ都称之为是一个进程。
一个程序可以运行多个,每一个都是一个进程。单例除外(比如任务管理器),操作系统 会为每个进程分配相关的资源,最重要的就是内存,还有什么文件描述符,IO端口号等都是资源。
什么时候需要执行了就把它从内存放到CPU里去执行(简单的说)。 程序load到内存里变成进程之后,就可以给它分配运行时需要的资源了,所以进程又是OS进行资源分配的基本单位(比较专业的说法)。
06_通俗理解线程
线程:一个程序里不同的执行路径。 main方法所开启的线程是主线程
多线程就是一个程序运行的时候会产生一个分支,不同的分支同时在运行,一个分支在等待程序输入,一个分支在存数据,一个分支在等待着网络的输入。 几个分支是同时运行的。
07_从底层角度理解线程
还是看上图,双击QQ.exe的时候,会load到内存变成一个进程,那程序是怎么开始执行这个进程的呢??
以下重要:
真正开始执行的时候,程序是以线程为单位来执行的,os会找到主线程(main方法),让它给到CPU去执行。
如果主线程中开启里其他线程,那线程之间就会来回的切换。 A线程交给CPU执行一会儿,B线程又交给CPU执行一会儿,这就是线程切换。
那么概念就出来了,一个进程包含多个线程。但是从概念上来说,进程是资源分配的基本单位(静态的概念),而线程是在进程的内部,是调度执行的基本单位(动态的概念),多个线程共享同一个进程里的资源。 就是共享了资源所以产生一堆麻烦事。
08_底层分析什么是线程的切换
线程切换也叫线程上下文切换
看这个图
作为一个程序来说,它有指令有数据,作为CPU来说它有几个重要的组成单元(计算单元 ARU、寄存器组 registers用来存储数据 的,PC也是一种寄存器 programme counter 用来存储我到底执行到哪条指令了。)
当我们执行到一个线程里,上图中所示,数据放在寄存器组里,指令放在PC。
所以 一个线程T1 执行的话,把T1的指令和数据放进CPU,然后CPU的计算单元对它进行计算,计算完了该输出输出,该做什么操作就做什么操作。
那根据os的线程调度算法,T1到时间了,它该走了,CPU不能继续服务T1了。(想要了解os的调度算法是什么样的,去看老马操作系统的课。。 )
那现在要换另外的线程T2了,要服务T2了,会发生什么呢? 把 T1线程的数据和指令地址存到缓存里去(看上图的cache),这个缓存后面会讲,把它理解成内存也可以。 总而言之就是放到旁边一个地方。再把T2的数据和指令放到CPU里,让CPU继续做计算。 所以CPU就是只管算就行,根据一个指令来算数据,其他它什么都不管。至于这个数据和指令是属于哪个线程的,CPU根本不管,这事归老大OS来管。
现在T2的时间到了,我想要T1回来继续计算,怎么办呢?
很简单撒,把T2相关是数据放进缓存,把原来T1相关的数据再放回CPU撒 (脑子始终有图)
这个过程需要os的调度,调度也是要消耗系统资源的,所以这就是线程切换的过程,专业名词叫 content switch(线程上下文切换)
09_单核CPU设定多线程是否有意义
单核CPU在一个时间点上只能跑一个线程,那设定多线程有意义吗?
当然有意义。
我们知道,线程就是一段程序做的一些操作撒,这些操作并不是全部都消耗CPU的,比如等待网络的输入就不需要(我在等待网络上传来数据呢,肯定不用消耗CPU)或者sleep的时候,也不需要,那这时候肯定要把时间让给别的线程去运行。
线程的分类:
有的叫 CPU密集型,啥意思?就是这个线程大量的时间都在做计算(如循环加减乘除),对CPU的利用率比较高。
还有的叫IO密集型,意思就是用大量的时间来等待输入输出,等待来数据之后很可能就做一个简单的拷贝。
这是两种不同的线程,不过大部分的线程即有IO 又有计算。
不同的线程要设置不同的数量,后面会讲。
10_线程数是不是越大越好_1
当然不是。线程切换是要消耗资源的。
11_线程数是不是越大越好_2
典型的多线程案例:一亿个数求和。
很简单,最起码我可以分成两份,一个线程算五千万个数,2个线程一起算不是更快么。
那问题又来了,线程数是不是设置的越多越好呢?
public class T01_MultiVSSingle_ContextSwitch {
private static double[] nums = new double[1_0000_0000];
private static Random r = new Random();
private static DecimalFormat df = new DecimalFormat("0.00");
static {
//给nums赋值
for (int i = 0; i < nums.length; i++) {
nums[i] = r.nextDouble();;
}
}
public static void m1(){
long start = System.currentTimeMillis();
double result = 0.0;
for (int i = 0