一.关于进程和线程
1.什么是进程
进程在操作系统中是这样进行管理的:
-
描述:PCB,叫做进程管理块,在创建进程时,建立 PCB,伴随进程运行的全过程,直到进程撤销而撤销.
(包含 PID,内存指针,文件描述符表,进程的状态,上下文,优先级,记账信息…)实际上这个 PCB 是一个非常大的结构体,属性非常多
-
组织:通过双向链表进行组织
虚拟地址空间:操作系统会直接依据真实的内存地址进行划分空间,分配给每一个进程.
2.多线程的概念
一个线程就是一个"执行流".每个线程之间都可以按照顺序执行自己的代码.多个线程之间"同时"执行着多份代码.
3.为什么要有多线程
有人会想,既然已经有进程了,也能够"并发编程",那为啥还需要进程呢?
线程比进程更轻量
对于进程来说,创建进程/销毁进程/调度进程,所需要的开销有点太大了.
进程是资源系统分配的基本单位
创建进程,需要分配资源;销毁进程,就需要释放资源.
如果频繁的创建销毁进程,资源开销势必就大.
于是就引入了线程Thread,也叫"轻量级进程"
4.线程同进程的比较
线程体现在"轻量":
- 创建线程比创建进程要更高效
- 销毁线程比销毁进程要更高效
- 调度线程比调度进程要更高效
对于线程来说, 创建和销毁都是不需要额外的申请和释放资源的,因为他们是共用同一个 进程中的资源.
线程和进程之间的关系:
-
进程包含线程,一个进程包含一个或多个线程.
里面的线程共用这个进程创建出来的资源,共享同一个内存空间. -
进程是系统分配的基本单元,线程是系统调度的最小单位
5.站在系统内核的角度,来看进程和线程
在 Linux 系统中,线程同样是使用 PCB 来描述的
进程1,对应了一个 PCB,若是在这个进程1中创建一个线程,相当于是再加了一个 PCB.
在操作系统内核角度来看,不分"线程还是进程",只认 PCB
当创建一个进程的时候,就是创建了一个 PCB 出来.
同时这个 PCB 也可以视为是当前进程中已经包含了一个线程了.(一个进程中至少得有一个线程)
属于同一个进程的线程之间,是共用一份内存空间.同时其他的进程(PCB)使用的是独立的内存空间
6.关于线程调度
系统是以线程为单位进行调度(以 PCB 为单位进行调度)
7.进程和线程之间的区别和联系:
- 进程包含线程,一个进程包含一个或多个线程.
- 每个进程都有独立的内存空间(虚拟地址空间),同一个进程里的多个线程共用一份系统资源(内存资源)
- 进程是操作系统进行资源分配的基本单位,而线程是系统进行调度的基本单位(PCB为单位进行调度)
二.创建线程
Java标准库中 Thread 类(创建线程的类)对操作系统提供的 API 进行了进一步的抽象和封装
- 每个线程都是一个独立的执行流
- 多个线程之间是"并发"执行
1.创建线程方式一:继承Thread,重写run()
static class MyThread extends Thread{
@Override
public void run() {
while(true){
try {
System.out.println("线程一");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
//上面是一个执行流(线程)
while(true){
try {
Thread.sleep(1000);
System.out.println("主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.创建线程方式二:实现Runnable接口,重写run
static class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
try {
System.out.println("线程一");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
t1.start();
while(true){
try {
Thread.sleep(1000);
System.out.println("主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.创建线程的方式三:使用匿名内部类的方式创建Thread的子类,重写run()
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
while(true){
try {
System.out.println("线程一");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
while(true){
try {
System.out.println("主线程");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.创建线程的方式四:使用匿名累不累的方式实现Runnable接口,重写run()
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
System.out.println("线程一");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
while(true){
try {
System.out.println("main 线程");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5.使用 lambda 表达式创建
和第三种道理一样
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while(true){
try {
System.out.println("线程一");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
while(true){
try {
System.out.println("main 线程");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//创建了 Thread 子类对象
6.分析线程创建过程,start()和run()的区别
在 java 中,使用 Thread 这个类的对象来表示一个操作系统中的线程.PCB 是在操作系统的内核中,描述线程的.
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
while (true){
System.out.println("1");
}
}
};
t.start();
//t.run();
//System.out.println("到我了没");
}
对于 start():
当程序运行后,首先系统会创建一个进程.这个进程里面已经包含了一个线程.这个线程执行的代码就是,main方法
对于 run():
run方法并未启动新线程,该程序中还是只有主线程,而且是顺序执行,当run()上面执行完毕后,下面的打印才会被执行.
7.并发执行的优势
提高运行速度