CPU:本质上是通过一些基础的门电路构成的:
电子开关=>基础门电路=>半加器=>全加器=>加法器=>进行加减乘除
CPU跑的快主要是因为当前这个CPU集成程度非常高.里面包含了非常多的门电路。
一、进程
操作系统是一个管理的软件。
对下,要管理好各种硬件设备;
对上,操作系统要给各种软件提供稳定的运行环境。
操作系统中提供的功能,是非常复杂的,这里重点介绍操作系统中针对"进程"这个软件资源的管理。
进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
进程(process)又叫任务(task)。
1.1 操作系统是如何管理进程的?(进程是如何管理的)
- 先描述一个进程(明确出一个进程上的一些相关属性)
操作系统里面主要都是通过C/C++
来实现的.此处的描述其实就是用的C语言中的"结构体"(也就和Java的类差不多)。操作系统中描述进程的这个结构体,称为"PCB
" (process control block
进程控制块)。
- 再组织若干个进程(使用一些数据结构,把很多描述进程的信息放在一起,方便进行增删改查)
典型的实现就是使用一个双向链表,把每个进程的PCB给串起来。操作系统的种类是很多,内部的实现也是各有不同,此处所讨论的情况是以Linux
这个系统为例。
所谓的“创建进程",就是先创建出PCB,然后把PCB加到双向链表中;
所谓的"销毁进程",就是找到链表上的PCB,并且从链表上删除;
所谓的“查看任务管理器"就是遍历链表。
1.2 PCB(进程控制块)中的一些属性
-
pid
(进程id):进程的身份标识—进程的身份证号; -
内存指针
:指明了进程要执行的指令/代码在内存哪里,以及进程中依赖的数据都在哪里。当运行一个exe
,此时操作系统就会把这个exe
加载到内存中,变成进程. -
文件描述符表
:程序运行过程中经常要和文件打交道(文件是在硬盘上),进程每次打开一个文件,就会在文件描述符表上多增加一项(这个文件描述符表就可以视为一个数组,里面的每个元素又是一个结构体,就对应一个文件的相关信息)。
一个进程只要一启动,不管代码中是否写了打开/操作文件的代码,都会默认的打开三个文件(系统自动打开的):标准输入(System.in),标准输出(System.out),标准错误(System.err)
;这个文件描述符表的下标,就称为文件描述符。
1.3 进程调度的基本过程
上面的属性是一些基础的属性.下面的一组属性,主要是为了能够实现进程调度:
状态:这个状态就描述了当前这个进程接下来应该怎么调度
就绪状态:随时可以去CPU上执行.
阻塞状态/睡眠状态:暂时不可以去CPU上执行.(Linux中的进程状态还有很多其他的…)
优先级:先给谁分配时间,后给谁分配时间,以及给谁分的多,给谁分的少
记账信息:统计每个进程,都分别被执行了多久,分别都执行了哪些指令,分别都排队等了多久;给进程调度提供指导依据的。
上下文:表示了上次进程被调度出CPU的时候,当时程序的执行状态,下次进程上CPU的时候,就可以恢复之前的状态,然后继续往下执行。
进程被调度出CPU之前,要先把CPU中的所有的寄存器中的数据都给保存到内存中(PCB的上下文字段中)相当于存档了.下次进程再被调度上CPU的时候,就可以从刚才的内存中恢复这些数据到寄存器中.相当于读档了.
存档+读档~~ 存档存储的游戏信息,就称为"上下文"。
1.4 进程间通信
进程的调度,其实就是操作系统在考虑CPU资源如何给各个进程分配
内存资源又是如何分配的呢?通过虚拟地址空间
由于操作系统上,同时运行着很多个进程,如果某个进程,出现了bug,进程崩溃了,是否会影响到其他进程呢?现代的操作系统(windows, linux, mac…)能够做到这一点,就是“进程的独立性"来保证的,就依仗了"虚拟地址空间"。
进程之间现在通过虚拟地址空间,已经各自隔离开了,但是在实际工作中,进程之间有的时候还是需要相互交互的。这就是进程间通信。
操作系统中提供了多种这样的进程间通信机制(有些机制是属于历史遗留的,已经不适合于现代的程序开发)
现在最主要使用的进程间通信方式,两种:
1.文件操作
2.网络操作(socket)
二、线程
1.线程是什么?
一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码。
例如:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
2.为什么要有线程?
我们的系统支持多任务了,程序猿也就需要"并发编程",通过多进程,是完全可以实现并发编程的,但是如果需要频繁的创建/销毁进程,这个事情成本是比较高的.如果需要频繁的调度进程,这个事情成本也是比较高的。
解决上述这个问题的方法:
1.进程池(数据库连接池.字符串常量池)
进程池虽然能解决上述问题,提高效率,同时也有问题.池子里的闲置进程,不使用的时候也在消耗系统资源.消耗的系统资源太多。
2.使用线程来实现并发编程,线程比进程更轻量.每个进程可以执行一个任务.每个线程也能执行一个任务(执行一段代码),也能够并发编程.
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.
3. 为啥线程比进程更轻量?(进程重量是重在哪里?)
重在资源申请释放(在仓库里找东西…)
线程是包含在进程中的.一个进程中的多个线程,共用同一份资源(同一份内存+文件)
只是创建进程的第一个线程的时候(由于要分配资源)成本是相对高的,后续这个进程中再创建其他线程,这个时候成本都是要更低一些,不必再分配资源。
4. (重点)线程与进程的区别?
- 进程包含线程,一个进程里面可以有一个线程,也可以有多个线程
- 进程和线程都是为了处理并发编程这样的场景,但是进程在频繁创建和销毁中,开销更高.线程开销更低.(线程比进程更轻量)
- 进程是系统分配资源(内存,文件资源…)基本单位.线程是系统调度执行的基本单位(CPU)。
- 进程具有独立性,每个进程有各自独立的独立的虚拟地址空间,一个进程挂了,不会影响其他进程。同一个进程有多个线程,共用同一块内存空间,一个线程挂了,可能影响到其他线程。
5. Java 的线程和操作系统线程的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread
类可以视为是对操作系统提供的 API
进行了进一步的抽象和封装.
线程之间是并发执行的
在一个进程中,至少会有一个线程.在一个java进程中,也是至少会有一个调用main方法的线程(这个线程不是你手动搞出来的)
如下面例子中,自己创建的thread 线程和自动创建的main线程,就是并发执行的关系(宏观上看起来是同时执行)。
class MyThread2 extends Thread{
@Override
public void run() {
//run方法中得逻辑,是在新创建出来的线程中被执行的代码
while (true){
System.out.println("hello thread");
try {
//这个休眠操作,就是强制的让线程进入阻塞状态,单位是ms 1s之内这个线程不会到cpu上执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test02 {
public static void main(String[] args) {
Thread thread = new MyThread2();
thread.start();
while (true){
//一个进程中,至少会有一个线程
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
现在两个线程,都是打印一条,就休眠个1s,每一轮1s时间到了之后,到底是先唤醒main还是 thread,这是不确定的(随机的)。
对操作系统来说,内部对于线程之间的调度顺序,在宏观上可以认为是随机的(抢占式执行)。这个随机性,会给多线程编程带来很多麻烦。
6. 创建线程的5种写法
方法1 继承 Thread 类
创建子类,继承自Thread
,并且重写run
方法.
run
方法中描述了这个线程内部要执行哪些代码.每个线程都是并发执行的. (各自执行各自的代码)因此就需要告知这个线程,你执行的代码是啥。并不是我一定义这个类,一写run方法,线程就创建出来,需要调用start方法,才是真正的在系统中创建了线程,才是真正开始执行上面的run操作。在调用start之前,系统中是没有创建出线程的。
class MyThread extends Thread{
@Override
public void run() {
//run方法中得逻辑,是在新创建出来的线程中被执行的代码
System.out.println("hello");
}
}
public class Test01 {
public static void main(String[] args) {
Thread t = new MyThread();
//执行到这里,才真正在系统中创建了线程
t.start();
}
}
方法2 实现 Runnable 接口
创建一个类,实现Runnable
接口,再创建Runnable
实例传给Thread
实例。
//通过Runnable来描述任务的内容,然后进一步再把描述好的任务交给Thread
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("hello");
}
}