目录

初步认识线程

初步认识线程

概念

一个线程就是一个 “执行流”,每一个线程之间都可以按照顺序执行自己的代码,多个线程之间可以 “同步” 执行多份代码

比如说,原本一个人做的事情,现在交给三个人一起做,那么这三个人就是线程

使用原因

那么为什么要有线程呢?我们直接使用进程不可以吗?

  • 需求增加,现如今更依赖于并发编程,来充分利用多核CPU
  • 取决于任务场景,需要很多任务同时完成,或者避免多余的等待

而线程相较于进程而言:

  • 线程是轻量级
  • 创建、销毁、调度更快

区别

进程和线程是有一定区别的,同时也存在一些联系

进程是包含线程的

每一个进程都必须至少有一个线程存在,也就是主线程

进程之间不共享内存空间,同一进程内的线程之间是共用所处进程的内存空间的

进程是系统分配资源的最小单位,线程是系统调度的最小单位

https://i-blog.csdnimg.cn/direct/159d6208cc3b451bbff4329495e2373c.png

我们还可以举一个简单的例子来解释:

假设我们的任务需求是组装汽车,一个人员对应一个组装台,如果此时汽车需求为100,那么这位组装人员就需要一个人组装100台汽车: 但是这样需要花费的时间就很长了

https://i-blog.csdnimg.cn/direct/2db5669cb0cc4165b9f33f313b3d38cd.png

  • 添加进程

当一个组装人员来完成这个任务时,我们需要很长的时间,于是此时,我们另外又聘请了一位组装人员B,B和A一样,分配了一个组装台,需要自己组装完整的车,在这种共同执行的条件下,每个人只需要组装50台车就可以完成任务

但是,这样也是有缺点的:

  • 每一个人员都需要熟悉完整的组装车的流程,培训的成本也就增加了
  • 我们需要为新聘请的人员增加一台新的组装台,需要的占地以及成本就增加了

https://i-blog.csdnimg.cn/direct/d73760d996f042b696d4ee3af8525d87.png

  • 添加线程

为了降低成本,我们还是先聘请人员B,让B和A合作,都在一台组装台进行工作,这样多的内存空间也就省下来了;同时,我们让A和B分别负责组装的一部分(比如一个负责车身,一个负责引擎),这样他们只需要培训他们负责的部分就可以了,培训成本就节省了

那么,我们可不可以为了节省成本不停的加入组装人员呢?

https://i-blog.csdnimg.cn/direct/6e9c05b553f44eb3b3f465e24f3eac3a.png

  • 添加线程

为了降低成本,我们还是先聘请人员B,让B和A合作,都在一台组装台进行工作,这样多的内存空间也就省下来了;同时,我们让A和B分别负责组装的一部分(比如一个负责车身,一个负责引擎),这样他们只需要培训他们负责的部分就可以了,培训成本就节省了

那么,我们可不可以为了节省成本不停的加入组装人员呢?

https://i-blog.csdnimg.cn/direct/4205d1db075847bcb549684d13bd95f9.png

代码演示

Java的线程和操作系统的线程是有一定区别的

操作系统: 实现了线程这样的机制,并且对用户层提供了一些API来满足用户的使用

Java: 使用标准库的 Thread类 ,可以看作对操作系统提供的API进行抽象和封装

对于多线程来说: 每一个线程都是一个独立的执行流,线程之间是 “并发” 执行的,

并发不是同时!!!

我们用一个代码来展示一下:

这里的线程就是 main(主线程)、创建的线程t

class MyThread1 extends Thread{
    @Override
    public void run(){
        //这里的代码就是线程要完成的工作
        //同时这里是一个线程
        System.out.println("hello world");
        while (true){
            System.out.println("thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        //这里也是一个线程
        Thread t = new MyThread1();
        t.start();//启动新的线程

        while(true){
            System.out.println("main");
            Thread.sleep(1000);
        }
    }
}

结果:

thread 和 main 是在并发式(并行/并发–>不确定)的进行

https://i-blog.csdnimg.cn/direct/fe06330b399247f58d4a7ac2d4146857.png

编写代码

创建线程也有很多方法

继承 Thread 类

直接使用 this 就表示当前线程对象的引用

  • 创建一个线程类并继承 Thread,继承之后要重写 run() 方法

    extends Thread

class MyThread1 extends Thread{
    @Override
    public void run(){
        //代码块
    }
}
  • 创建线程实例,是一个 Thread类,因为继承,所以可以直接创建 MyThread1类,来实现创建 Thread类
Thread t = new MyThread1();
  • 启动线程
t.start();//启动新的线程

实现 Runnable 接口

this 表示的是 MyRunnable 的引用,需要使用 Thread.currentThread()

  • 实现Runnable接口

    implements Runnable

class MyRunnable implements Runnable{
    @Override
    public void run(){
        //代码块
    }
}
  • 创建实例,调用方法启动线程(因为不是继承,所以直接创建的 MyRunnable类 不是 Thread类)
Thread t = new Thread(new MyRunnable());
t.start();

其他创建方法

  • 在匿名内部类中创建 Thread 子类对象
Thread t = new Thread(){
    @Override
    public void run(){
        //代码块
    }
};
t.start();
  • 在匿名内部类中创建 Runnable 子类对象
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
       //代码块
});
t.start();
  • 使用 lambda 表达式来创建 Runnable 子类对象
Thread t = new Thread(()->{
   //代码块
});
t.start();

优缺点

优点

  1. 提高响应性: 主线程保持响应,避免因耗时任务导致界面卡顿
  2. 资源利用率高: 线程共享内存和资源,减少资源消耗,适合处理大量并发任务
  3. 提升性能: 多核CPU上并行执行任务,充分利用硬件资源,加快处理速度
  4. 简化设计: 将复杂任务分解为多个线程,简化程序结构,便于维护
  5. 实时处理: 适合实时系统,能够同时处理多个任务,满足实时性要求

缺点

  1. 复杂性高: 线程同步和资源竞争问题增加了开发和调试难度
  2. 调试困难: 多线程程序的非确定性行为使得问题难以复现和定位
  3. 资源竞争: 多个线程访问共享资源时,可能导致死锁或数据不一致
  4. 上下文切换开销: 频繁的线程切换会消耗CPU资源,影响性能
  5. 内存消耗: 每个线程需要独立的栈空间,线程过多时会增加内存负担
  6. 线程安全问题: 需要额外措施(如锁、信号量)来保证数据一致性,增加了复杂性

常见面试题

进程 线程 的区别

可以从定义、资源分配、开销、独立性、并发性以及应用场景来回答

进程线程
定义进程是操作系统分配资源的基本单位,是程序的一次执行实例 每个进程有独立的内存空间、文件描述符、系统资源线程是进程内的执行单元,是CPU调度的基本单位 线程共享进程的内存空间和资源,有自己的栈和寄存器
资源分配独立的内存和资源共享进程的内存和资源
开销(创建、切换)较大较小
独立性高,进程之间互相隔离低,线程之间共享资源
应用场景需要高隔离性的任务需要高并发和资源共享的任务