一、进程
1.1 基本概念
一个程序运行起来,在操作系统中就会有一个对应的进程。进程就是一个跑起来的应用程序。
要想让这个程序运行起来就必须给进程分配系统资源,包括但不限于cpu、硬盘、带宽等。进程也可以视为操作系统进行资源分配的基本单位。
1.2 进程的描述
操作系统中通常是使用PCB(进程控制块)这样的结构体来描述进程的,操作系统通常会使用链表这样的数据结构将多个PCB穿起来。
任务管理器查看进程列表: 遍历链表节点,显示相应信息。
创建新的进程: 新的进程创建PCB中并连接到链表中。
销毁进程: 将链表上对应的节点删除。
PCB包含很多信息,此处讨论几个比较关心的~~
(1)pid
进程的id/标识符,同一机器同一时刻进程的标识符肯定是不同的。
(2)内存指针
内存指针指向程序要运行的指令以及数据的地址。
(3)文件描述符表
一个进程运行时会操作一些文件,这是会有一个类似"顺序表"这样的数据结构记录都打开了哪些文件。
(4)状态、优先级、上下文、记账信息
这些属性都是用来支持进程调度的。
- 状态:进程有两个状态,就绪和阻塞。(随叫随到和有事约不出来,可以这样理解)
- 优先级:系统分配给不同进程的时间资源存在倾斜。
- 上下文:存档,进程在cpu上执行也会有中间结果,在进程切换出cpu之前会把这些结果保存在PCB的上下文中(寄存器->内存)。读档,进程重回cpu执行,就会继续执行(内存->寄存器),将存档恢复,其中一个寄存器时PC程序计数器,记录指令执行位置。
- 记账信息:操作系统避免一些进程一直吃不到资源,会进行类似的统计,给资源分配少的进程适当多分配一点。
二、线程
(1)JVM没有提供多进程编程的api(也不是完全没有,也提供了非常粗糙的多进程操作)。
(2)java生态中也不太鼓励多进程编程。
既然java中不鼓励多进程编程,那么我们下面了解下一个概念"线程"。
2.1 基本概念
当前的cpu都是多核心的cpu,这就需要我们需要通过一定的编程技巧将需要完成的任务拆解成多个部分,并分别让它们在不同的核心上运行。否则多核心就形同虚设了。
通过多进程编程其实可以起到并发编程的效果,因为进程可以被调度到不同的核心上执行,但是也带来了新的麻烦。
比如说:在服务器开发的圈子里,这种并发编程的需求是常见的,作为一个服务器要为多个客户端提供服务。如果同一时间来了很多客户端服务器却只能使用一个核心工作,速度会比较慢。一个典型的做法是为连上服务器的客户端都创建一个进程从而提供服务,这个客户端断开了,就销毁对应的进程。
接上述,如果这个服务器有客户端频繁地来来去去,那么就要不停地创建/销毁进程,然而这是需要时间的操作,多了就会影响程序地运行速度。
引入线程的初心就是解决进程太重量的问题!线程也被称为轻量级线程。线程可以理解为进程的一部分,一个进程可以包含一个线程或多个线程。前面陈述的描述进程的PCB事实上严格来说是用来描述线程的,一个线程对应一个PCB,多个PCB即多个线程描述进程,前面先提出来也是为了便于后续的理解。
(1)pid:对应于每一个线程,一个线程一个pid。
(2)内存指针和文件描述符表:同一个进程的若干个线程,这里的内存指针和文件描述符表都是相同的。内存指针和文件描述符表就是代表一个线程用到的内存以及硬盘的资源,线程之间的内存及硬盘资源是共享的,第一个线程来进行创建,后面创建的线程就直接共享。
(3)状态、优先级、上下文、记账信息:这些用来调度的属性都是每一个线程独有的,每一个线程都是不同的,线程也是系统执行调度的基本单位。
(4)tgid:是进程的id,同一个进程的tgid是一个。
为什么说线程比进程轻量?为什么说线程创建/销毁的开销要比进程小?
从上述也可以看出来,一个进程包含多个线程,并且创建每一个进程都要涉及资源分配及释放,但是线程除了一个进程内的第一个线程需要分配资源外,其余同进程的线程的资源是共享的,这样的话创建线程就省去资源分配/释放步骤了。
资源分配/释放时比较费力的活,操作系统内核有一系列专门的数据结构来管理空闲内存,需要在这些数据结构中查找遍历找到合适空间才能申请,这个过程中还有各种各样的问题。
注意:线程不是越多越好,越多的线程系统调度的时间就会变长,线程是抢占式调度的。
进程与线程的区别:
- 一个进程包含至少一个线程。
- 一个线程出现异常,那么与其在一个进程内的其它线程都得玩完。进程与进程之间不影响。
- 线程轻量,进程重量。创建第一个线程需要内存及硬盘资源,但是后面创建的同一个进程的线程资源上是共享的,但是创建不同的进程就需要不同的资源。
- 线程是系统调度执行的基本单位,进程是资源分配管理的基本单位。
感觉自己总结的比较一般,可以看看https://blog.csdn.net/ThinkWon/article/details/102021274这篇博客,讲得非常详细。
2.2 创建线程的五种写法
(1)子类继承
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t=new MyThread();
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
(2)实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable());
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
(3)匿名内部类
public class Demo3 {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
(4)匿名内部类(实现接口)
public class Demo4 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
(5)lambda表达式
public class Demo5 {
public static void main(String[] args) {
Thread t=new Thread(()->{
while (true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.3 Thread类中的其它属性和方法
2.4 线程中止
(1)自己写
public class Demo10 {
//这里不会出现变量捕获的情况是因为,这里单纯就是内部类使用外部类变量天经地义
private static boolean isRunning=true;
public static void main(String[] args) {
//boolean isRunning=true;
//这里不能如此使用,因为处于lambda表达式中的变量捕获的情况
//isRunning变量必须是final或者事实final 但是后面会修改isRunning的值 因此如果这样使用编译就会报错
Thread t=new Thread(()->{
while (isRunning) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程已经终止");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("准备终止线程");
isRunning=false;
}
}
(2)使用Thread类自带的方法
public class Demo11 {
//使用这种方法控制线程终止对比布尔类型变量的优势在于
//如果Thread匿名类中的sleep时间很长,当你修改了布尔类型变量后还要等一段时间才能终止线程
//但是使用Thread类中自带方法不需如此
public static void main(String[] args) {
Thread t=new Thread(()->{
while (!Thread.currentThread().isInterrupted())
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//throw new RuntimeException(e);
//通过interrupt方法会修改循环条件的布尔值终止线程
//之后唤醒sleep的线程,之后的处理由线程自己决定,如果catch中不终止线程,那么下一轮循环线程还是会执行
//因为sleep会修改循环条件
//catch里面写break;直接终止
//或者写一些代码再终止等等
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt();
}
}
2.5 线程等待
public class Demo12 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread end");
});
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main end");
}
}
上述代码为了使main线程与t线程之间结束的顺序是固定的,使用了t.join()方法,相当于main线程必须等到t线程执行完后才可以继续执行。
Java中对线程的状态分为了六种:
- NEW:Thread对象有了,但是还没有调用start方法创建线程。
- TERMINATED:线程已经终止了,但是Thread对象还在未被销毁。
- RUNNABLE:线程就绪。
- WAITING:线程处于死等状态。
- TIMED_WAITING:线程处于等待状态并且等待时间有限制。
- BLOCKED:线程阻塞,处在因为锁而阻塞。