操作系统
操作系统是一组做计算机资源管理的软件的统称。目前常见的操作系统有:Windows系列、Unix系列、Linux系列、OSX系列、Android系列、iOS系列、鸿蒙等。
进程
每个应用程序运行于现代操作系统之上时,操作系统会提供一种抽象,好像系统上只有这个程序在运行,所有的硬件资源都被这个程序在使用。这种假象是通过抽象了一个进程的概念来完成的,进程可以说是计算机科学中最重要和最成功的概念之一。
进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
跑起来的程序才叫进程
CPU 分配 —— 进程调度
并行:微观上同一时刻,两个核心上的进程,就是同时进行的
并行:微观上,同一时刻,一个核心只能运行一个进程。但是能够对进程快速切换。
进程状态
就绪状态:
运行状态:
阻塞状态:
内存分配 —— 内存管理
进程的虚拟地址空间:解决进程之间相互影响的问题。引入虚拟地址空间,地址越界能及时发现。
进程间通信
如上所述,进程是操作系统进行资源分配的最小单位,这意味着各个进程互相之间是无法感受到对方存在的,这就是操作系统抽象出进程这一概念的初衷,这样便带来了进程之间互相具备”隔离性
(Isolation)“。
但现代的应用,要完成一个复杂的业务需求,往往无法通过一个进程独立完成,总是需要进程和进程进行配合地达到应用的目的,如此,进程之间就需要有进行“信息交换“的需求。进程间通信的需求就应运而生。
目前,主流操作系统提供的进程通信机制有如下:
- 管道
- 共享内存
- 文件
- 网络
- 信号量
- 信号
其中,网络是一种相对特殊的 IPC 机制,它除了支持同主机两个进程间通信,还支持同一网络内部非同一主机上的进程间进行通信。
线程
“轻量级进程”。解决并发编程问题的前提下,让创建,销毁,调度的速度更快一些。和进程相比,剩下了申请资源、释放资源的操作。
一个线程就是一个 "执行流” 。 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.
多进程也能实现 并发编程, 但是线程比进程更轻量.
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
线程相关
- 同一个进程里的多个线程之间,共用了进程的同一份资源(内存和文件描述符表)。内存是指,线程1new的对象,在线程2,3,4里都可以直接使用。文件描述符表指的是,线程1打开的文件,在线程2,3,4里都可以直接使用。
- 操作系统在调度的时候,是以线程为单位进行的。线程是操作系统调度执行的基本单位,每个线程也都有自己的执行逻辑(执行流)。
- 一个进程里可以有多个线程,一个线程不能存在多个进程里(可以进程间通信)
- 增加线程的数量,也不一定能提高速度。CPU核心数量有限(每个核心操作一个线程)
- 会有线程安全问题(线程不安全)。进程不会出现问题。
线程模型天然就是资源共享的,所以多线程容易争抢同一个资源
进程模型天然就是资源隔离的。进程间通信,访问同一个资源时,可能处理问题。
- 如果一个线程抛异常,可能使整个进程杀掉。
创建一个线程
下图创建了一个简单的线程:
这个代码通过t.start(),主线程调用了t.start,创建了一个新的线程。新的线程调用了t.run。当run执行完毕,线程自动销毁。
并发编程
下图中创建了一个线程,在线程里循环打印“thread”,然后再main方法里循环打印“main”,运行程序会得到什么:
可以看到,控制台里main和thread都出现了。这也就是程序在“并行”。
从打印结果来看,“main”和“thread”并不是一前一后出现的。这源于操作系统调度线程时的“抢占式执行”,线程的具体先后顺序不确定,取决于操作系统调度器的具体实现策略。而从代码的角度来看,线程调度顺序好像是“随机”的一样。
start和run之间的区别:
start是真正创建了一个线程(从系统中创建的),线程是独立的执行流。
run只是描述了线程要做的事情。如果在main中直接调用run,那么不会创建线程。
Java中创建线程的写法:
- 继承Thread类,重写run方法。
- 实现Runnable接口
让线程和线程的任务分离,解耦合
-
使用匿名内部类,继承Thread
-
使用匿名内部类,实现Runnable接口
这个写法和2本质相同
- 使用Lambd表达式
推荐写法,简单~
Thread方法
Thread的常见属性
前台线程会阻止进程结束,后台线程(守护线程)不会。代码里手动创建的线程和main里的线程都是默认前台线程。
这个代码中t.setDaemon(true); t.start();
的顺序不能更换。
因为当一个线程已经启动后,不能再将其设置为守护(后台)线程。
中断一个程序
中断(终止)的意思是,不是让线程立即就停止,而是通知线程应该停止。是否线程真正停止,取决于线程的具体代码写法。
interrupt方法会触发sleep内部的异常,让sleep提前返回(被唤醒)
但是,这个程序在执行的时候,结果似乎和想象的不太一样:
此时我们发现,线程在被打断之后,又继续执行了,这是怎么回事?
原因是这样的,此时程序里的interrupt会做两件事:
- 把线程内部的标志位(boolean)设置成true
- 如果线程处于sleep状态,则会唤醒sleep
但是sleep在被唤醒的时候,会将刚才设置成true的标志位,再重新设置回false(清空标志位)。 因此,这就导致当sleep的异常被catch完之后,循环仍然还要执行!!!!!
所以,这也就是前面所说的为什么中断一个程序,只是通知这个程序该被中断。
线程对中断请求的回应:
- 忽略中断请求
- 立即相应请求
- 稍后相应请求
等待一个线程 join()
假设再让main阻塞之前,t线程就已经结束,那么join()不会阻塞main方法。也就是说,join是否进行阻塞也是看情况的
获取当前线程的引用
休眠当前线程
线程的状态
线程的状态总共有六种
- NEW 表示创建了Thread对象,但还是没有调用start(内核里还没创建对应的PCB)
- TERMINATED 表示内核中的pcb已经执行完毕了,但是Thread对象还在
一个线程只能start一次 (规定)
- RUNNABLE 表示可运行的 (不是RUNNING)
RUNNABLE表示两种状态:
a. 正在CPU上执行的
b. 在就绪队列里,随时可以去CPU上执行
- WAITING
- TIMED_WAITING
- BLOCKED
以上三种都表示不同原因的阻塞(线程PCB正在阻塞队列中)