目录
1. 进程概念
- 一个运行起来的程序就是一个进程;
- 进程是操作系统资源分配的基本单位;
- 描述一个进程,里面的成员有:
①pid,每一个进程都有一个唯一的身份标识;
②内存指针,用来表示当前的进程所用到的资源内存是哪一部分;
③文件描述表:进程每次打开一个文件就会产生一个“文件描述符”,标识这个被打开的文件,一个进程可能会打开多个文件,对应一组文件描述符,把这些文件描述符放到一个顺序表中,就构成了文件描述表,描述的是进程再运行的时候使用了哪些硬盘上的资源,硬盘上存储的数据,往往就是以文件为单位进行整理的。
【内存指针描述的是内存上的资源,文件描述表描述的是硬盘上的资源】
④与进程调度有关的属性。
进程调度的属性有:- 进程的状态:阻塞态和就绪态,就绪态是可以直接在cpu上运行的
- 进程的优先级
- 进程的上下文,理解为保护现场,当执行进程1的时候转去执行进程2,此时要保护进程1的运行结构,实际上是保护寄存器的结构
- 进程的记账信息,方便统计进程的执行信息
- 并发和并行
并发:同一时刻,1个核心,先执行进程1,执行一会再去执行进程2,执行一会再去执行进程3,此时只要切换的速度足够快,看起来,进程1 2 3就是“同时”执行的。
并行:同一时刻,2个核心,同时执行2个进程,这2个进程就是并行执行的。
并发和并行完全是操作系统自身控制的,程序员感知不到,所有很多时候把并发+并行统称为并发。
2. 线程的概念
- 由于创建、销毁、调度进程比较慢,因此引入“轻量级的进程”——线程。
- 一个进程中的多个线程之间,共用同一份系统资源,也就是说只有在进程启动的时候,创建第一个进程的时候需要花成本去申请系统资源,后续再创建线程的时候不需要申请资源了,这样创建/销毁的效率就提高了。
- 进程是操作系统调度运行的基本单位。
3. 进程和线程的区别(面试常问)
- (关系)进程包含线程;
- (内存空间和文件描述表)进程有自己独立的内存空间和文件描述表;同一个进程中的多个线程之间之间共享同一份地址空间和文件描述表;
- (操作系统基本单位)进程是操作系统资源分配的基本单位;线程是操作系统调度运行的基本单位;
- (独立性)进程之间具有独立性,一个进程挂了,不会影响到其他的进程;但是同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响到其他线程。
4. 创建线程的五种写法
1. 继承 Thread, 重写 run
class MyThread extends Thread {
public void run() {
while (true) {
System.out.println("tttt");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
while (true) {
System.out.println("mmmm");
}
}
}
分析:继承Thread类,但是实际上Thread类实现了Runnable接口,所有与第2种方法创建多线程类似。
2. 实现 Runnable, 重写 run
class MyThread2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("ttt");
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Thread t = new Thread(new MyThread2());
t.start();
while (true) {
System.out.println("mmm");
}
}
}
分析:创建新线程的时候,传入了一个实例化类的对象,并且这个类实现了Runnable接口重写了run方法。
3. 继承 Thread, 重写 run, 使用匿名内部类
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
System.out.println("ttt");
}
};
t.start();
}
}
4. 实现 Runnable, 重写 run, 使用匿名内部类
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("tttt");
}
});
t.start();
}
}
分析:大括号{放在哪里就是针对哪个类创建的匿名内部类。
3是针对Thread()创建的匿名内部类
4是针对Runnable()创建的匿名内部类
5. 使用 lambda 表达式(最推荐的写法、最简单最直观)
lambda表达式的本质就是一个匿名函数(没有名字的函数,一次性的)。
在Java中,函数(方法)无法脱离类,但是lambda是一个例外。
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> { //和Runnable匿名内部类写法差不多
while (true) {
System.out.println("ttt");
}
});
t.start();
while(true) {
System.out.println("mmm");
}
}
}
运行:
分析:t.start表示:启动线程,再进程中搞了另外一个新流水线,新的流水线开始并发执行另外一个逻辑。
注意:start会创建新的线程,而run不会创建新的线程,
start调用操作系统的api,创建新线程,新的线程调用run;
run理解成是线程的入口方法。
上述代码涉及到2个线程:
①main方法所对应的线程(一个进程里面至少有一个线程)
②通过t.start创建的新的线程
点击这里运行程序,实际上是idea对应的进程,创建了一个java进程,这个java进程来执行程序员写的代码,再这个java进程里面有2个线程,一个是main线程,一个是t线程,2个线程并发执行 ”每个线程是一个独立的执行流“,至于先打印t还是先打印m是不确定的,因为多个线程再cpu上调度执行的顺序是随机的。
5. 使用jconsole分析线程
- 在jdk中找到jconsole
- 打开,找到要分析的进程(只能分析java进程,不能分析非java写的进程)
可以看到
第一个进程是:idea进程
第二个进程是:自己写的java进程
第三个进程是:jconsole进程 - 打开要分析的进程,分析其包含的线程
除了标记处的main线程和Thread-0线程之外,其余的都是jvm自己创建的线程。 - 分析堆栈追踪信息
通过堆栈追踪,可以知道这2线程中的代码执行到哪里了。