1. 进程理解
进程是操作系统对一个正在运行的程序的一种抽象,通俗的说,进程就是运行起来的程序;
同一时刻系统中的进程,有很多,而操作系统就得给他们安排好,
也就是****进程是操作系统进行资源分配的基本单位。
1.1 进程管理
进程管理 = 描述 + 组织
描述:详细的表示一个进程中有哪些属性/信息,而结构体就是用来描述的;
一个结构体对象对应一个进程,结构体也叫PCB(进程控制块)。
组织:通过一定的数据结构,将若干个用来描述的实体组织到一起,进行增删改查;
系统通常会使用** 双向链表** 这样的结构来将PCB组织到一起。
创建一个进程,本质上就是创建PCB,给进程分配资源赋值到PCB中,并且加入到链表上
销毁一个进程,本质上就是从链表上删除对应的PCB结点,是否PCB持有的资源
查看任务管理器的进程列表,本质上就是在遍历这个链表
一个进程也可以是多个PCB,系统管理PCB的链表也可能是多个
1.2 PCB属性
(1)PID:进程的身份表示
一个主机,同一时刻,进程的pid是唯一的(相当于身份证号),可以通过pid区分进程
(2)内存指针:描述了这个进程使用的内存空间是哪个范围(分配空间范围)
(3)文件描述符:描述了这个进程打开了哪些文件(文件就是存储在硬盘上的数据)
下面几点都是和 “进程调度” 有关的
进程调度就是通过 “并行” 和 “并发” 的方式,让计算机可以同时执行多个进程
并行执行:每个CPU核心上,都可以独立运行一个进程
多个CPU核心上,就可以独立运行多个进程
并发执行:一个CPU核心,先运行进程1,再运行进程2,再运行进程3...
宏观上,同时运行多个程序;微观上,同一时刻运行一个程序
(4)进程状态: R(可执行状态) S(可中断睡眠状态) X(退出状态) D T Z
阻塞状态的进程,无法被调度到CPU上执行
就绪状态的进程,才可以在CPU上执行
(5)进程优先级:优先给谁安排先执行
系统调度的时候,就会根据优先级,来给进程安排执行时间
创建进程时,也可以通过系统调用来干预优先级(相对的)
(6)进程上下文:主要存储调度出CPU之前,寄存器的信息(把寄存器的信息保存到内存中)等到这个进程下次恢复到CPU上次执行时,将内存数据恢复到寄存器中
通俗的说就是“读档”和“存档”
进程在CPU上执行,要切换到别的进程,就需要保存当前运行结果的中间结果(存档),下次再到他执行时,就恢复之前的中间结果(读档),然后继续执行。
(7)进程的记账信息:记录进程在CPU上执行时长
再来辅助决定,进程是在CPU上继续执行,还是调度出去
** 1.3 虚拟地址空间&进程间通信**
为了不出现进程间互相影响,指针越界操作等情况,就需要让每个进程都有各自的内存空间,不让这些进程的活动范围重叠,给每个进程划分的内存空间,就叫做 “虚拟地址空间”(不是真实的物理内存地址),通过专门的设备MMU来完成虚拟地址,到物理之前的映射
使用虚拟地址空间,就让进程间存在了 “隔离性”
(一个进程不能直接访问另一个进程的内存数据,防止干扰操作,提高系统稳定性)
但这样就会导致进程之间很难进行交互,为了解决这个问题又有了“进程间通信”
进程间通信:核心原理就是,找多个进程都可以访问到的公共资源,然后基于公共资源进行数据交换。
**主流操作系统提供的进程通信机制有:文件、Socket(网卡)**、管道、消息队列、磁盘、信号量、信号
2.多线程理解(Thread)
2.1 线程概念
一个线程就是一个执行流(按一定顺序执行的一组代码),
每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行多份代码
同一个进程中的这些线程,共用一份系统资源(内存+文件)
进程是 资源分配 的基本单位
线程是 调度执行 的基本单位
2.2 线程的作用
首先是,因为“并发编程”可以更充分利用CPU
其次,多进程虽然也可以实现并发编程,但线程比进程更轻量
(创建、销毁、调度线程都比进程更快)
所以,使用多线程:
1.能够充分利用多核CPU,能够提高效率
2.只是创建第一个线程时,需要申请资源,后续再创建新的进程,都是共用一份资源
销毁线程时,也是销毁到最后一个的时候,才真正释放资源,前面的线程销毁,都不必真的释放资源
最后,又有了更好的方法,“线程池”和“协程”
2.3 进程和线程的区别(*)
1.进程包含线程(多个)
2.线程比进程更轻量(创建、销毁、调度更快)
3.同一个进程的多个线程之间共用同一份内存/文件资源,
** 进程和进程之间,则是独立的内存/文件资源**
4.进程是操作系统资源分配的基本单位
** 线程是操作系统调度执行的基本单位**
2.4 多进程需要注意的问题
多线程,一定程度下,线程数目越多,效率越高;但CPU核心数是有限的,当线程数目多到一定程度的时候,CPU核心就被占满了,线程调度开销太大,反而会使效率变慢
当两个线程去争夺同一个资源是,此时两个线程就出现了问题,这就会使“线程不安全”
如果某个线程出现异常,并且异常没有处理好,此时就可能会使整个进程崩溃,此时其他线程也会运行出现问题
3. 线程创建
3.1 创建线程(继承Thread类)
继承Thread类,重写run方法
class MyThread extends Thread { @Override public void run() { System.out.println("thread!"); } }
标准库中,提供了一个Thread类,使用的时候就可以继承这个类
Thread类,相当于是对操作系统中的线程进行的封装
重写run方法,run是Thread父类中已有的方法,但是这里要重写
run中的逻辑,就是这个线程要执行的工作
创建子类,重写run方法,相当于“安排任务”
public class Demo01 { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } }
创建MyThread实例,并不会在系统中真的创建一个线程
而是,调用start方法的时候,才可以真正创建线程
新的线程才会执行run方法中的逻辑,直到run中逻辑执行完,新的线程才会运行结束
class MyThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
while(true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
抢占式执行
** 3.2 Jconsole工具查看线程**
(1)找到自己电脑上JDK安装路径中的bin—》jconsole.exe打开
(2)点击本地进程,选择当前项目连接,连接成功后,就可以查看当前项目中的线程
(3)选择查看线程信息
** 3.3 创建线程其他方法**
3.3.1 实现Runnable接口,重写run方法
** 创建了Runnable实例,将runnable实例作为参数传给线程**
将线程要干的工作 和 线程本身 分开了,使用Runnable来专门表示“线程要完成的工作”
这种方法的好处是,降低了耦合性,如果以后要修改代码,改动相对较少
只需要将修改后的Runnable传给其他的实体就可以了
并且如果是多个线程干一样的工作,Runnable也更适合
class MyRunnable implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
while (true) {
System.out.println("hello main!");
Thread.sleep(1000);
}
}
}
3.3.2 使用匿名内部类,实现创建Thread子类的方式
创建Thread的子类,同时实例化出一个对象
public class Demo03 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while(true) {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
}
}
3.3.3 使用匿名内部类,实现Runnable接口的方法
匿名内部类的实例,作为构造方法的参数
public class Demo04 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
3.3.4 lambda表达式
public class Demo05 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
4. Thread类的常见方法
4.1 Thread常见构造方法
方法说明Thread()创建线程对象Thread(Runnable target)使用Runnable对象创建线程对象Thread(String name)创建线程对象,并命名Thread(Runnable target, String name)使用Runnable对象创建线程对象,并命名
public class demo01 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"这是线程名");
thread.start();
}
}
打开Jconsole.exe,可以看到线程的名字,就是我们自己命名出来的
** 4.2 Thread的常见属性**
属性获取方法IDgetId()名称getName()状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAlive()是否被中断isInterrupted()
(1)getId() 这个是java中给Thread对象安排的身份标识,和操作系统内核中PCB的pid,以及操作系统提供的线程API的线程id都不是一个
(2)setDaemon() 判断是否是守护线程。****默认创建的线程是“前台线程”,前台线程会阻止进程退出,如果main运行完,前台线程还没完,进程不会退出;如果是“后台线程”,后台线程不会阻止进程退出,如果main等其他的前台进程执行完,此时即使后台线程没执行完,也会退出。
“只要有一个前台(main也是一个前台),进程就还活着,前台没了,后台也会没”
**设置操作得在start之前,如果线程启动,就不能修改 **
public class demo01 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"这是线程名");
//使用setDaemon设置为后台线程
//设置操作得在start之前,如果线程启动,就不能改
thread.setDaemon(true);
thread.start();
System.out.println("main线程执行结束!");
}
}
(3)isAlive() 判断内核中线程是否存活。Thread对象,虽然和内核中的线程,是一一对应关系,但是生命周期并非完全相同;Thread对象存在了,内核里的线程不一定有,调用start方法,内核线程才存在。当内核里的线程执行完(run运行完),内核的线程就销毁了,但是Thread对象还在
(4)写一段代码看看Thread属性
public class Demo02 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"我的线程");
thread.start();
System.out.println(thread.getId());
System.out.println(thread.getName());
System.out.println(thread.getPriority());
System.out.println(thread.getState());
System.out.println(thread.isDaemon());
System.out.println(thread.isAlive());
System.out.println(thread.isInterrupted());
}
}
** 4.3 线程启动(start和run的区别)*
调用start才会真正创建线程,不调用start就没创建线程(在内核里创建PCB)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello thread!");
}
}
public class Demo03 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();//创建一个新线程,调用run
//myThread.run();//在当前线程调用run
}
}
start 和run的区别(*)
直接调用run,并没有创建线程,只是在原来的线程中运行代码
调用start,则是创建了线程,在新线程中执行代码(和原来的线程是并发的)
作用功能不同:
- run方法的作用是描述线程具体要执行的任务;
- start方法的作用是真正的去申请系统线程
运行结果不同:
- run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
- start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。
4.4 线程中断(isInterrupted)
线程中断,本质上是让run方法尽快结束,而不是run执行一半,强制结束
有两种方法
(1)直接自己定义一个标志位,作为线程是否结束的标记
public class Demo04 {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!isQuit) {
System.out.println("hello tread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread线程执行完了!");
});
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("设置让thread线程结束!");
}
}
** (2)使用标准库中自带的标记位interrupt**
public class Demo05 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello tread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread线程执行完了!");
});
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("设置让thread线程结束!");
}
}
运行程序,发现出现了异常,这是因为
interrupt方法的行为,有两种情况
1.thread线程在运行状态会设置,标志位为true
** 2.thread线程在阻塞状态(sleep)不会设置标志位,而是触发一个InterruptedException**
这个异常会把sleep提前唤醒
所以,要想顺利结束循环,由线程自身的代码进行判定处理
线程自身可以(1)立即结束线程(break)
(2)啥都不干
(3)稍后处理(sleep,break)
4.5 线程等待(join)
线程之间的调度顺序,是不确定的,可以通过join来控制线程之间的结束顺序
方法说明join()等待线程结束join(long millis)等待线程结束,最多等millios毫秒join(long millis, int nanos)也可以等待更高精度的时间 毫秒+纳秒
** 在main中,调用join效果就是,让main线程阻塞等待,等到thread执行完,main才继续执行**
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("main线程join之前");
thread.join();
System.out.println("main线程join之后");
}
}
4.6 获取当前线程引用
currentThread(); 获取到当前这个线程对应的Thread对象的引用
5. 线程状态
NEW: 安排了工作, 还未开始行动(Thread对象创建出来了,但是内核的PCB还没创建,(还没真正创建线程))
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作(就绪状态(正在CPU上运行+在就绪队列中排队))
BLOCKED: 这几个都表示排队等着其他事情(等待锁的时候进入阻塞状态)
WAITING: 这几个都表示排队等着其他事情(特殊的阻塞状态,调用wait)
TIMED_WAITING: 这几个都表示排队等着其他事情(按照一定的时间,进行阻塞sleep)
TERMINATED: 工作完成了(内核的PCB销毁了,但是Thread对象还在)
public class Demo07 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//tread开始之前,得到的就是 NEW
System.out.println(thread.getState());
thread.start();
Thread.sleep(50);
//tread正在工作,得到的是 RUNNABLE
System.out.println(thread.getState());
thread.join();
//tread结束之后,得到的状态是 TERMINATED
System.out.println(thread.getState());
}
}
版权归原作者 快到锅里来呀 所有, 如有侵权,请联系我们删除。