计算机的组成元件都是些冷冰冰的硬件,就像人体的躯干一样,而软件才是计算机的灵魂。
我们把软件分为系统软件和应用软件,操作系统就是系统软件的一种,只不过它是最基础的软件,其他所有软件都要运行在操作系统上。
那软件是什么呢?简言之,软件就是程序的载体。我们写的hello world等代码,打包出来就是软件,就能安装在各种设备上,然后运行在操作系统上。
那操作系统又是啥呢?凭什么让它管理我写的软件呢?我们就来了解下。
进程和线程的概念
操作系统是啥?是软件,只不过它是专门管理软件的软件。
操作系统有两个作用:
负责跟系统硬件打交道,把用户的各种操作(比如鼠标点击、键盘输入等)翻译给计算机硬件,然后将结果告诉用户;
管理其他软件。
那操作系统是怎么管理软件呢?通过管理进程!
我们知道,软件是程序的载体,而每一个程序都是一个进程。
每一个程序都有一个进程。
我们可以把一个软件理解为一个家庭,那么这个家庭就是一个进程。
那么,我们家就是一个进程,在这个进程中,有三口人:我、我爸、我妈。
那照这个意思来说,村长就是操作系统了?
虽然不太合适,不过也可以这么理解。如果这样的话,那是不是意味着村长每次下命令,都只能指挥一个家庭?因为一个家庭是一个进程,村长又是管理家庭的。
这听着不太对啊,难道村长不能让我单独去做一件事吗?必须让我们整个家庭一起去做一件事吗?一家人一起去做一件事,是不是有点浪费资源?
确实浪费资源,实际上,村长是可以指挥单独一个人去做一件事的,那么这个人就叫做一个线程。
那么,村长能单独调动的最小单位就不是一个家庭了,而是一个人。也就是说:线程是操作系统能调度的最小单元。
到这里,我们就知道了:每个程序都有一个进程,线程是进程的一部分,线程是操作系统能调度的最小单元。
那么,为啥要这么干呢?这么做村长不累吗?
线程的优点
使用线程可以提高程序的运行速度,节省时间和成本。
首先,村长肯定累,虽然比不过生产队的驴,不过也差不多了。
但是,人家操作系统是计算机,是拥有光速的男人,你就不要操心人家了。
举个例子,假如没有线程,村长让我家去挨家挨户发个开放三胎的通知,我们会怎么办呢?
三个人一起挨家挨户地去烦他们,全村 1000 户,每家 2 分钟,一共 2000 分钟,大概 33.333 小时(其中 3 无限循环),这不吃不喝一天都干不完。
那有人就纳闷了,你这就发个通知,也就嘴炮两句的事,用得着拖家带口一起上阵吗?如果你有爷爷奶奶是不是准备 5 个一起上?5 个人一起去通知一个只有两口人的家庭?发通知的比接通知的人都多,何苦呢?
所以我们就要上线程啊。
上了线程后,我们每个人都是一个线程,就可以三个人一起上,这样时间直接变为原来的 1/3,一天就搞定了,从而节省了时间。
而且原来 33 小时搞定的事情,现在 11 小时搞定,等价于白白增加了 22 小时的寿命,等价于节省了成本。
所以,引入线程,就等价于变串行为并行,能提高速度,节省时间。
串行:一件事同一时刻只有一个人做。
并行:一件事同一时刻有多个人做。
有人说,这样的话,岂不是线程越多越好吗?越多就等于同时有更多的人去做,这样肯定更快啊。
错!
还是刚刚的例子,全村 1000 家,你能用 1001 个人去通知吗?显然不能。最重要的是:计算机无法让那么多线程同时工作。
这是啥意思呢?
比如说,一个 16 核的 CPU,最多只能让 16 个线程同时工作。即使开了 100 个线程,同一个时刻也是只有 16 个线程同时工作。
有人说,不对啊,我开了 100 个线程打印数字,发现它们都同时打印了。
那只是你以为而已。其实这里面是有个调度算法的,计算机通过一定的调度算法,让你这 100 个线程看起来像是每个都在执行,这个我们在后面会细讲。
再举个例子,大家都用迅雷下载过电影,其实每个下载流都是一个线程,是不是越多越好呢?
不是!
因为网速就 100k/s,你开一个线程下载,就是 100k/s,你开两个线程下载,那么网速就均分,就变成每个 50k /s,这有啥意义呢?原来 100k/s 的还能先下载个看着,然后等另一部下载呢。
但是,如果你是在计算数学题,比如 100 道题目,你就可以开启 10 个线程,每个线程计算 10 道题目,然后把结果合并即可。
其实,说白了:如果你的程序存在着资源竞争,开启多线程是无益的;如果你的程序不存在资源竞争,开启多线程是有益的。
比如上面的下载,都在争着用网络,你即使开启 250 个线程,也 no egg use。你多开线程能增加网速吗?而上面的数学题,彼此都是独立的,就可以多开几个线程来同时完成。但是如果上面 100 道题目的下一题需要依赖上一题的结果,那就别开多线程了,没用!
上述说到的资源竞争,就是多线程的缺点,那这个咋避免呢?
多线程的问题
资源冲突:多个线程在同一时刻操作同一个资源。
还是上面的例子,假如三个人都同时通知到同一家,这个算不算资源冲突呢?
不算,因为这个只是访问资源,不是操作资源,操作资源指的是对资源进行了修改。比如:
// 定义资源i
int i = 10;
// 访问资源,没有修改i
println(i);
// 操作资源,对i进行了修改
i = 1;
发生冲突的资源,就叫临界资源。比如,如果发生了冲突,上述的i就是临界资源。
上述我们说了,线程是进程的一部分,是操作系统调度的最小单位。是调度的最小单位,不是持有资源的最小单位。也就是说,资源都是在进程中的,进程是持有资源的最小单位。
还是我家,我们每个人都可以自由行动,但是我家的粮仓却只能放在我家,不可能每个人都持有一个粮仓,粮仓就是资源,所以进程是持有资源的最小单位。线程可以使用资源,却不能持有资源。
控制器控制程序流按顺序执行,程序流指的就是线程,每个线程内部都有一个缓存,用来存放自己需要的数据,当每个线程被执行的时候,CPU 就会把这个线程需要的数据读入缓存中,等到执行完毕,就再把缓存的数据写回到内存中。
每一个进程都有一块内存,这个内存是所有线程共享的。每一个线程都有一个缓存,这个缓存是自己独占的。
假如我们有个进程 P,进程 P