进程
进程的初步认识
- 对于操作系统来说,一个任务就是一个进程(Process)
- 进程是操作系统分配资源的最小单位
- 进程是操作系统中非常重要的软件资源,把一个可执行程序跑起来 (.exe文件) 系统就会创建一个对应的进程,如果这个程序执行结束系统随之会销毁所对应的进程
- 所以进程就可以看做是一个程序执行的"过程"
以Linux为例:
- 每创建一个进程,就会同时创建至少一个PCB这样的类的实例
- 系统会使用双向链表将PCB对象串到一起
- 双击一个exe文件,创建一个进程的时候,本质是在内核中先创建一个PCB对象,然后将PCB根据进程管理加到这个链表中
- 关闭一个程序,就会在这个链表中先找到这个PCB然后删除他
PCB的数据结构
- pid 进程的身份标识
- 一组内存指针,指向该进程持有的一些重要数据(包括要执行的指令 指令依赖的数据) 在内存中的位置
- 状态
- 优先级
- 进程的记账信息
- 上下文
调度器用到哪些PCB信息
进程的管理一定是先描述,再组织,而描述就是一句PCB对象里的信息
实现进程调度目的就是为了让很多的进程能够很好的在有限的CPU上进行并发执行
优先级
判断该进程在就绪队列的为位置,是优先执行还是稍后执行
并发与并行
-
一个CPU同一时刻只能执行一个进程的指令
-
并发执行
并发执行是在宏观上看到是并发,在微观上来看,很多进程是串行执行的,只不过CPU的执行速度很快,人在宏观上感知不到
-
并行执行
并行执行宏观是同时执行,微观也是同时执行,如计算子有俩个CPU就可以同时执行俩个进程的指令
内核态与用户态
- 操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态。
- 用户程序的权限最低,称为用户态。
- 简而言之就是人宏观可以感受到的变化状态就可以称之为用户态, 人宏观感受不到的称之为内核态
- 引进内核态就是为了操作系统的安全性
上下文
- 上下文简单说来就是一个环境,进程在时间片轮转切换时,由于每个进程运行环境不同,就涉及到转换前后的上下文环境的切换就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容。
- 切换时需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
进程状态
就绪状态
: 正早CPU上执行, 或者即将执行睡眠状态(阻塞)
: 这些进程没法在CPU上执行,而是在等待深度睡眠状态
: 进程在长时间忙于IO操作,没精力例会CPU退出状态(终止)
: 进程已经被销毁了
时间片
- 操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
- 任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。
记账信息
- 记录了进程在CPU上一共执行了多长时间, 通过这个时间来限制不要让某个进程霸占CPU太久,导致其他进程无法执行
线程
认识线程
进程是系统分配资源的最小单位,线程是系统调度的最小单位。
- 一个进程内的线程之间是可以共享资源的。每个进程至少有一个线程存在,即主线程。
得出结论:
- 线程是包含在进程中的
- 当创建一个进程的时候,会自动随之至少创建一个线程(主线程)
- 一个进程可以有多个线程(通过每个线程的PCB对象的tgroupid来判断那几个线程属于一个进程) 把属于同一个进程的多个线程称为 线程组
- 每个线程都有自己要执行的逻辑(指令),每个线程是独立的"执行流"
- 同一进程中的很多线程自检,是共享了一些资源(不是全部资源)
- 共享的资源主要有俩部分: 一是内存资源, 二是打开的文件
- 不共享的资源有: PCB对象里的一个信息如上下文 优先级 状态 记账信息, 二是 内存中一块特殊区域的栈
为什么要引入线程
- 线程可以理解为一个轻量级进程,也是实现并发编程的一种方式
- 因为线程之间可以共享大部分资源,不需要为新线程分配很多的资源,所以创建一个线程的成本较低,而创建一个新进程就需要把它所需要的资源再创建一份.
- 在实际编程的时候,多线程的方式比多进程方式更常见,也更高效
- 那一个进程对多有多少个线程?
- 和CPU个数有关
- 和线程执行的任务类型也有关系(是CPU密集型 还是 IO密集型)
-
引入线程也是有一定缺点的
降低了进程的安全性, 比如因为线程是部分资源共享, 当俩个或者多个线程需要相同资源时,就有可能发生不安全的情况
多种创建线程的方式
java标准库提供了一个
Thread类
帮助我们实现线程通过Thread 与
Runnable
/ lambda 方式创建的线程本质上没什么区别核心都是依靠thread类 ,但是细节上(站在耦合性的角度) 在使用Runnable和 Lambda创建的线程在run中没有涉及到Thread相关的内容, 这就意味着很容易把这些逻辑从线程中剥离出来,去搭配其他并发编程的方式来执行,也可能会容易改成不并发的方式去执行
多线程的方式:
Thread类 与 Runnable接口
public class ThreadTest3 {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello");
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello");
}
}
public static void main(String[] args) {
//1定义一个类继承Thread
Thread t = new MyThread();
t.start();
//2定义匿名内部类
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("hello");
}
};
t1.start();
//3 lambda表达式
Thread t2 = new Thread(() -> {
System.out.println("hello");
});
t2.start();
//4定义一个类继续Runnable接口,但注意要将runnable对象关联到Thread对象上
Runnable r1 = new MyRunnable();
Thread t3 = new Thread(r1);
t3.start();
//5匿名内部类继续Runnable接口
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
Thread t4 = new Thread(r2);
t4.start();
//6对Runnable进行Lambda表达式
Runnable r3 = () -> {
System.out.println("hello");
};
Thread t5 = new Thread(r3);
t5.start();
}
}
使用Callable/Future/FutureTask创建线程
- FutureTask是继承于Future的 所以使用FutureTask和Callable可以创建一个带返回值得多线程结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
static class MyThread implements Callable<String> {
private int ticket = 10;
@Override
public String call() throws Exception {
while (this.ticket > 0) {
System.out.println("余票为:" + this.ticket--);
}
return "票空";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new MyThread());
Thread thread = new Thread(task);
Thread thread1 = new Thread(task);
thread.start();
thread1.start();
//get方法会阻塞 直到task运行结束
System.out.println(task.get());
}
}
多线程的优势-增加运行速度
public class ThreadTest2 {
private static long count = 10_0000_0000;
public static void main(String[] args) {
Danxianc();
Duoxianc();
}
private static void Danxianc() {
long beg = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
int a;
a = i;
}
for (int i = 0; i < count; i++) {
int b;
b = i;
}
System.out.println(System.currentTimeMillis() - beg + "ms");
}
private static void Duoxianc() {
long beg = System.currentTimeMillis();
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
int a;
a = i;
}
}
};
Thread thread1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
int b;
b = i;
}
}
};
thread.start();
thread1.start();
//让方法这个线程等待,等thread 和thread1这两线程跑完自己再执行
try {
thread.join();
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() - beg + "ms");
}
}
运行结果:
1753ms
1044ms