一、什么是进程/任务(Process/Task)
1、进程定义
1)进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程。进程是操作系统进行资源分配的最小单位。
2)进程是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消。
.exe是一个可执行文件(程序),当我们双击这个文件时,这个程序就跑起来,于是系统就形成了一个进程(跑起来的程序就是进程)
2、描述进程
使用结构体(进程控制块抽象 Process Control Block)/类,把一个进程有哪些信息表示出来。
组织进程:使用一定的数据结构。
// 以下代码是 Java 代码的伪码形式,重在说明,无法直接运行 class PCB { // 进程的唯一标识 —— pid; // 进程关联的程序信息,例如哪个程序,加载到内存中的区域等 // 分配给该资源使用的各个资源 // 进度调度信息(留待下面讲解) }
pid:(内存指针)当前这个进程使用的内存那一部分(使用那些内存上的资源)
文件描述符表:把文件放到一个顺序表这样的结构里,构成了文件描述符表,
文件:比如硬盘上的存储的数据,往往是以文件为单位进行整理的。
操作系统在通过这种数据结构,例如线性表、搜索树等将PCB对象组织起来,方便管理时进行增删查改的操作。
操作系统使用双向链表组织PCB:
1)创建一个进程,就是创建一个链表的节点;
2)销毁一个进程,就是删除一个链表的节点;
3)遍历进程列表,就是在遍历链表。
3、进程间通信(Inter Process Commuication)
1) 产生原因:要完成一个复杂的业务需求:往往无法通过一个进程独立完成,总是需进程和进程进行配合达到应用的目的,进程之间需要进行“信息交换”。
2)通俗理解:就是在隔离的前提下,找一个公共区域,让两个进程借助这个公共区区来完成数据交换。
3)主流操作系统提供的进程通信机制:(在Java中,主要使用文件、socket来完成进程通信)
1、管道
2、共享内存
3、文件
4、网络
5、信号量
6、信号
4、进程调度(PCB的属性)
1)进程状态:就绪态、阻塞态
2)进程优先级:最高、其次、最低
3)进程的上下文:指在进程运行过程中,CPU内部的一系列寄存器的值。 存档
4)统计了每个进程,在CPU上执行多久,可以作为调度的参考依据。
5、区分并行和并发
并行:同一时刻,两个核心,同时执行两个进程,此时这两个进程就是并行执行的;
并发:一个核心,先执行进程1;执行一会之后,再去执行进程2,3等,此时只要切换速度足够快,看起来,他们就是“同时”执行的。
并发+并行统称为并发,完全是操作系统自身控制的。
二、认识线程(Thread)
1、定义
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
举例:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。 如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找 来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队, 自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。 此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别 排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
注:每个线程都是一个独立的执行流,多个线程之间,也是并发执行的。
多个线程可能是多个CPU核心上,同时运行,也可能是在一个CPU核心上,通过快速调度,进行运行,操作系统真正调度的,是在调度线程,而不是进程。
2、产生原因
首先, "并发编程" 成为 "刚需".
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源.
- 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编 程.
- 其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量. 创建线程比创建进程更快. 销毁线程比销毁进程更快. 调度线程比调度进程更快.
- 最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程" (Coroutine)
3、 进程与线程的区别
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
详解请点击: https://blog.csdn.net/leyhe/article/details/139046831?spm=1001.2014.3001.5502
4、 如何在Java中实现线程?
(1)继承Thread类实现多线程
继承Thread类,然后重写run方法.(由于Java单继承的特性,这种方式用的比较少)
public class MyThread extends Thread {
public MyThread() {
}
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+":"+i);
}
}
public static void main(String[] args) {
MyThread mThread1=new MyThread();
MyThread mThread2=new MyThread();
MyThread myThread3=new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}
(2)实现Runnable()接口定制执行目标(target)类,实现其run()方法
推荐此方式。两个特点:
-
a.覆写Runnable接口实现多线程可以避免单继承局限
-
b.实现Runnable()可以更好的体现共享的概念
-
c.当执行目标类实现Runnable接口,此时执行目标(target)类和Thread是代理模式(子类负责真是业务的操作,thread负责资源调度与线程创建辅助真实业务。
public class MyTarget implements Runnable{ public static int count=20; public void run() { while(count>0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--); } } public static void main(String[] args) { MyThread target=new MyTarget(); Thread mThread1=new Thread(target,"线程1"); Thread mThread2=new Thread(target,"线程2"); Thread mThread3=new Thread(target,"线程3"); mThread1.start(); mThread2.start(); myThread3.start(); } }
(3)实现Callable接口创建多线程(JDK1.5)
a.执行目标核心方法叫call()方法
b.有返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyTarget implements Callable<String> {
private int count = 20;
@Override
public String call() throws Exception {
for (int i = count; i > 0; i--) {
// Thread.yield();
System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
}
return "sale out";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> callable =new MyTarget();
FutureTask <String>futureTask=new FutureTask<>(callable);
Thread mThread=new Thread(futureTask);
Thread mThread2=new Thread(futureTask);
Thread mThread3=new Thread(futureTask);
// mThread.setName("hhh");
mThread.start();
mThread2.start();
mThread3.start();
System.out.println(futureTask.get());
}
}
其他变形
- 匿名内部类创建
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
- 匿名内部类创建 Runnable 子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
- lambda 表达式创建 Runnable 子类对象
// 使用 lambda 表达式创建 Runnable 子类对象 Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象")); Thread t4 = new Thread(() -> { System.out.println("使用匿名类创建 Thread 子类对象"); });
5、多线程的优势
可以观察多线程在一些场合下是可以提高程序的整体运行效率的。
使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳. serial 串行的完成一系列运算. concurrency 使用两个线程并行的完成同样的运算
public class ThreadAdvantage {
// 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
private static final long count = 10_0000_0000;
public static void main(String[] args) throws InterruptedException {
// 使用并发方式
concurrency();
// 使用串行方式
serial();
}
private static void concurrency() throws InterruptedException {
long begin = System.nanoTime();
// 利用一个线程计算 a 的值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
}
});
thread.start();
// 主线程内计算 b 的值
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
// 等待 thread 线程运行结束
thread.join();
// 统计耗时
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("并发: %f 毫秒%n", ms);
}
private static void serial() {
// 全部在主线程内计算 a、b 的值
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
private static void serial() {
// 全部在主线程内计算 a、b 的值
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("串行: %f 毫秒%n", ms);
}
}
并发: 399.651856 毫秒
串行: 720.616911 毫秒