进程与线程的认识

进程是资源分配的基本单位,具有独立的内存空间和文件描述符表,而线程是调度执行的基本单位,共享进程资源,轻量级且能提高执行效率。多线程创建包括继承Thread类、实现Runnable接口以及使用lambda表达式。线程状态包括新建、可运行、阻塞、等待、超时等待和终止。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录
一.进程

  • 定义
  • 进程在操作系统内核中管理形式
  • PCB里进程的特征
  • 进程运行形式
  • 进程调度相关属性

二.线程

  • 定义
  • 线程优点
  • 线程缺点

三. 多线程创建形式
四.进程与线程区别
五.Thread常见方法
六.Thread常见属性
七.线程状态

一.进程

  • 定义:进程指一个跑起来的程序,程序是静态的,它作为系统的一种资源永远存在,而进程是动态的,它会随着程序的结束而销毁.进程不仅包含这个程序的实体,它还包含这个程序所占有的系统资源.(例如同一程序,同一时刻两次运行,就表示他们是两个独立的进程)

  • 进程在操作系统内核里如何管理?
    (描述+组织)描述指使用结构体PCB结构控制块来描述进程属性,组织指的是通过双向链表把PCB串在一起.
    创建一个进程,本质上就是创建一个PCB这样的结构体对象,把它插入到链表中
    销毁一个进程,本质上就是把链表上的PCB结点删除掉
    任务管理器查看进程列表,本质上就是遍历这个链表

  • PCB里面有哪些描述了进程的特征?
    在这里插入图片描述
    1.PID:进程身份标识符(唯一的一个数字),一个主机同一时刻这些进程的PID是唯一的,通过PID来区分进程
    2.内存指针:指向了自己的内存(进程所占的系统资源,指向该进程持有的一些重要数据在内存中的位置)
    3.文件描述符表:硬盘上的文件等其他资源
    一个进程可能是一个PCB,也可能是对应多个PCB,系统管理PCB链表也不一定是一个

  • 进程运行的形式
    1.并行:微观上同一时刻,两个核心进程同时执行
    2.并发:微观上来说,同一时刻,一个核心上只能运行一个进程,但是如果进程之间快速切换,我们是感知不到的,就好像这几个进程同时进行.

在这里插入代码片
//串行与并行区别
public class Demo6 {
    public static final long cont=100000000L;

    public static void main(String[] args) {
        //serial();
        concurrency();
    }
    //串行任务
    public static void serial(){
        long beg=System.currentTimeMillis();
        long a=0;
        for (int i = 0; i < cont; i++) {
            a++;
        }
        a=0;
        for (int i = 0; i < cont; i++) {
            a++;
        }
        long end=System.currentTimeMillis();
        System.out.println("时间间隔:"+(end-beg));
    }
    //并发
    public static void concurrency(){
        long beg=System.currentTimeMillis();
        Thread t=new Thread(()->{
            long a=0;
            for (int i = 0; i < cont; i++) {
                a++;
            }
        });
        Thread t1=new Thread(()->{
            long a=0;
            for (int i = 0; i < cont; i++) {
                a++;
            }
        });
        t.start();
        t1.start();
        long end=System.currentTimeMillis();
        System.out.println("时间间隔:"+(end-beg));
    }
}

使用两个线程最终消耗的时间不一定是一个线程消耗时间的50%,创建线程实例,也是有开销的,串行执行,没有额外创建线程,并发执行创建了两个线程.如果计算量小,计算的快,创建线程开销影响更大,多线程提升就不明显

  • 进程调度相关属性
    1.进程状态
    就绪状态:进程随时准备好执行
    运行状态:正在运行
    阻塞状态:短时间内无法在CPU上执行
    2.优先级
    操作系统进行调度时也是有优先级的,创建进程时,可以通过一些系统调度相对的干扰优先级
    3.上下文
    本质上是存档内容,CPU的寄存器的值
    操作系统在进程切换时,需要把进程执行的中间状态记录下来保存好,下次这个进程在CPU上运行时,就可以恢复上次状态继续执行
    4.记账信息
    操作系统统计每个进程在CPU上占用的时间和执行的指令数目,以便调整下次各个进程改如何调度

  • 虚拟地址空间(避免进程之间相互产生影响)
    程序所获取的地址并不是真实的物理内存地址,而是通过MMU映射到的物理地址,是抽象虚拟的.
    在这里插入图片描述
    一旦进程访问越界,操作系统发现当前地址超过进程1访问范围,直接向进程反馈一个错误,这样会防止干扰操作,提高系统稳定性,不会出现一个进程有bug而影响其他进程

  • 进程之间是相互独立的,如果需要进程通信,就会引出进程间通信机制
    指一个或多个进程都可以访问到公共空间,基于这个公共空间进行数据间的交换即可.

二.线程(thread)

  • 定义:线程被包含在进程中,一个进程默认有一个线程,也可以有多个线程.一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.

  • 为什么要引入线程?
    CPU进入多核新时代,如果用多进程去提高执行速度,会造成消耗资源多,速度慢,而频繁的创建与销毁进程这个操作比较低效.

  • 多线程的优点
    1.可以充分利用多核CPU,提高效率
    2.只是创建第一个进程时需要申请资源,后续在创建新线程,都是共用一份资源,节省申请资源的开销.销毁线程时,也只是销毁到最后一个才真正释放资源,前面的线程不必真正释放资源

  • 多线程的缺点
    1.当线程数目越来越多时,CPU核心数有限,当线程数目达到一定程度时,CPU核心已经被吃满,此时增加线程,无法提高效率,反而会因为线程太多,线程调度开销太大,影响效率.
    2.线程之间可能会相互影响,如果两个线程同时修改变量,也容易产生线程不安全问题.
    3.如果某个线程发生意外,很可能把整个线程带走,其他线程无法工作

  • 操作系统内核:通过一组PCB来描述一个进程,每一个PCB对应一个线程.一个进程至少有一个线程,也可以是多个.这一组的内存指针和文件描述符表其实是同一份东西,而状态,优先级,上下文则是每一个线程都会有一个

三.谈谈线程和进程区别

   1.进程包含线程
   2.线程比进程更轻量(创建更快,销毁更快)
   3.同一个进程多个线程共用一份内存/文件资源,进程与进程之间则是相互独立的
   4.**进程是资源分配的基本单位,线程是调度执行的基本单位**

四.多线程创建方式

  • 继承Thread类
在这里插入代码片
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("这里是线程运行实例");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread t=new MyThread();//创建一个Mythread实例,并不是在系统中直接创建
        t.start();//调用start方法时,才真正创建出一个新线程
    }
}

1.重写run方法,run是Thread父类里面已经有的方法,run里面的逻辑就是这个线程要执行的工作,Java运行一次程序,就是启动一个进程,一个进程里面至少会有一个线程,或着多个线程,main方法里面的线程也叫做主线程.
2.(main主线程与Mythread创建出来的新线程都是并发执行的)调用start方法另外启动一个线程在执行run方法,新线程是一个单独的执行流,和现有线程执行不想关.

在这里插入代码片
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) {
        MyThread t=new MyThread();
        t.start();
        while (true){
            System.out.println("main");
            try {
                Thread.sleep(1000);//执行sleep,线程进入阻塞,sleep时间到,线程恢复就绪状态,但是哪个线程谁先谁后不一定,抢占式执行
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  • 实现Runable接口
在这里插入代码片
class MyRunnable implements Runnable{
    @Override
    public void run() {
    while (true){
        System.out.println("Hello");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
         }
       }
    }
}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable runnable=new MyRunnable();
        Thread t=new Thread(runnable);
        t.start();
        Thread t1=new Thread(runnable);
        t1.start();
        while (true){
            System.out.println("main");
            Thread.sleep(1000);
        }

    }
}

1.runnable当作t变量里面的参数,主要是把线程的任务与线程本身分开,使用runnable专门表示线程完成的任务.前面的继承thread写法,把线程要完成的工作与线程本身耦合在一起,如果要对代码进行调整,代码改动比较大,而runnable只需把它传给其他实体进行改动即可.
2.如果想要多个线程干一样的任务,runnable是最合适的.

  • 继承Thread类,使用匿名内部类
在这里插入代码片
public class Demo3 {
    public static void main(String[] args) {
        Thread t=new Thread(){ //创建Thread子类,同时实例化出一个对象
            @Override
            public void run() {
                while (true){
                    System.out.printf("hello");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
}
  • 实现Runnable接口,使用匿名内部类
在这里插入代码片
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");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            }
        });
        t.start();
    }
}

  • 通过lambda表达式
在这里插入代码片
public class Demo5 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
          while (true){
              System.out.printf("hello");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
        });
        t.start();
    }
}

lambda本质上是匿名函数,()表示函数形参,{}表示函数体,->表示它是一个lambda

三.Thread常见方法

1.给线程起名字
Thread t3 = new Thread(“这是我的名字”);
Thread t4 = new Thread(new MyRunnable(), “这是我的名字”);
2.创建一个实例对象
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());

六.Thread常见属性

1.ID:这是java中给Thread对象安排的身份标识,和操作系统内核中的PCB中的PID,以及操作系统提供的线程API的线程id不是一回事.
2.isDaemon()判断是不是守护线程
1.默认创建的线程是前台线程,前台线程会阻止进程退出,如果是后台线程,后台线程不阻止进程退出,如果前台线程执行完,这个时候不论后台线程有没有执行完,程序都会退出
2.main运行完前台线程还没完,程序不会退出
setDaemon()设置成后台线程,但是必须在调用start开始之前设置,如果前台线程启动了,就改变不了了.
3.isAlive()内核线程是否存活
Thread对象虽然和内核中的线程是一 一对应关系,但是生命周期并不是完全对应的.
Thread对象实例化出来,但是内核线程不一定会有,调用start方法,内核线程才会有,当你内核线程执行完,run运行完,内核线程就销毁了,但是Thread对象还在.所以说Thread对象的生命周期比线程长.
4.start方法
直接调用run方法并没有创建线程,只是在原来线程里面运行代码,调用start,则是创建了线程,在新线程中执行代码(和原来线程是并发的)


```java
在这里插入代码片
class MyThread1 extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
       MyThread1 t=new MyThread1();
       t.run();
      // t.start();
       while (true){
           System.out.println("main");
           Thread.sleep(1000);
       }
    }
}

属性的展示:

在这里插入代码片
public class Demo8 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            while (true){
                System.out.println("Thread");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"这是线程");
        t.start();
        System.out.println(t.getId());
        System.out.println(t.getName());
        System.out.println(t.getPriority());
        System.out.println(t.getState());
        System.out.println(t.isDaemon());//是否是守护线程
        System.out.println(t.isAlive());//是否存活
        System.out.println(t.isInterrupted());//是否线程中断
    }
}

5.线程中断
定于:线程中断本质上是让run方法尽快结束,而不是run执行一半强制结束(取决于run里面具体实现)
线程中断方式

  • 定义一个标志位,作为线程是否结束的标记
在这里插入代码片
public class Demo9 {
    public static boolean Quit=false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (!Quit){
                System.out.println("Hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程执行完");
        });
        t.start();
        Thread.sleep(5000);
        Quit=true;
        System.out.println("线程结束");
    }
}

  • 使用标准库自带的标志位
在这里插入代码片
public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("Hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程执行完");
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();//通过它中断线程,设标志位为true
        System.out.println("线程停止");
    }
}

t.interrupt();方法行为有两种情况

  • t线程在运行状态,会设置,将它的标志位由原来flase改为true

  • t线程在阻塞状态,会触发异常,不会设置标志位(不是没设置,而是interrupt会设置标志位,但是sleep等这些阻塞方法会清除这个标志位,表面上看是没有设置标志位,实际上设置了**
    代码只打印了日志,并没有结束循环,因此线程还在继续执行中,Java中中断线程并不是强制性的,可以有以下几种方法

  • 立即结束线程,break

  • 不理会

  • 稍后处理sleep

6.线程等待join方法
线程之间调度顺序是不确定的,通过一些特殊操作,来对线程执行顺序进行干预

在这里插入代码片
public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        System.out.println("main线程join之前");
        t.join();
        System.out.println("main线程join之后");
    }
}

在main中调用了join,让main方法阻塞等待,等t执行完了,main函数继续执行
如果调用join之前,t线程已经结束了,main是不需要阻塞等待的
public void join(long millis) 等待线程结束,最多等 millis 毫秒,可以等待,但是是有时间限制,过期不候

7.sleep()指定休息时间,阻塞一会.
sleep将线程添加到阻塞队列里,当挪回就绪队列时,并不是立即去CPU上执行,还是得看系统啥时候调度到这个线程

七.线程状态

  • NEW: 安排了工作, 还未开始行动 (Thread对象创建出来,内核PCB还未创建)
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. (cpu上运行+在就绪队列里面排队的)
  • BLOCKED:这几个都表示排队等着其他事情 (等待锁)
  • WAITING: 这几个都表示排队等着其他事情 (特殊阻塞状态,调用wait)
  • TIMED_WAITING: 这几个都表示排队等着其他事情 (按照一定时间进行阻塞)
  • TERMINATED: 工作完成了.(内核PCB销毁,但是Thread对象还在)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值