Skip to content

一、 线程相关概念 ​

1.1 程序、进程与线程 ​

  • 程序:是为完成特定任务、用某种语言编写的一组指令的集合,即我们写的静态代码。

  • 进程:

    1. 指运行中的程序。如启动QQ,操作系统会为其分配独立的内存空间。
    2. 进程是程序的一次执行过程,是动态的,有其产生、存在和消亡的生命周期。
  • 线程:

    1. 由进程创建,是进程的一个实体。
    2. 一个进程可以拥有多个线程(如一个QQ进程可以同时打开多个聊天窗口)。

1.2 单线程、多线程、并发与并行 ​

  • 单线程:同一时刻,只允许执行一个线程。
  • 多线程:同一时刻,可以执行多个线程。
  • 并发:单核CPU实现的多任务。同一时刻,多个任务交替执行,造成“同时”的错觉。
  • 并行:多核CPU实现的多任务。同一时刻,多个任务真正同时执行。

二、 线程的创建与使用 ​

2.1 创建线程的两种方式 ​

  1. 继承 Thread 类,重写 run 方法。
  2. 实现 Runnable 接口,重写 run 方法。

2.2 方式一:继承 Thread 类 ​

  • 步骤:

    1. 定义一个类继承 Thread。
    2. 重写 run 方法,在其中编写线程要执行的任务。
    3. 创建该类的实例。
    4. 调用实例的 start() 方法启动线程。
  • 注意:

    • start() 方法会调用底层的 start0() 方法,使线程进入可运行状态,由CPU调度执行。
    • 直接调用 run() 方法仅是普通方法调用,不会启动新线程。

2.3 方式二:实现 Runnable 接口 ​

  • 步骤:

    1. 定义一个类实现 Runnable 接口。
    2. 重写 run 方法。
    3. 创建该类的实例。
    4. 将此实例作为参数传递给 Thread 对象的构造器,创建 Thread 对象。
    5. 调用 Thread 对象的 start() 方法启动线程。
  • 原理:使用了代理模式。Thread 类本身实现了 Runnable 接口,其 run 方法会调用传入的 Runnable 对象的 run 方法。

  • 优势:

    1. 避免Java单继承的限制。
    2. 更适合多个线程共享同一个资源的场景(只需创建一个 Runnable 实例,传递给多个 Thread 对象)。

三、 线程常用方法 ​

3.1 第一组常用方法 ​

  1. setName(String name) / getName():设置/获取线程名称。
  2. start():启动线程,使其进入可运行状态。
  3. run():线程执行体,由JVM调用。
  4. setPriority(int newPriority) / getPriority():设置/获取线程优先级(范围1-10)。
  5. sleep(long millis):静态方法。让当前线程休眠指定的毫秒数。
  6. interrupt():中断线程,但并非强制结束。通常用于中断正在 sleep 或 wait 的线程,使其抛出 InterruptedException。

3.2 第二组常用方法 ​

  1. yield() :线程的礼让。让出CPU,给其他线程执行机会,但礼让成功与否取决于CPU调度,不一定成功。

  2. join() :线程的插队。调用此方法的线程会阻塞当前线程(如主线程),直到调用者执行完毕,当前线程才继续执行。

    • 应用场景:主线程需要等待子线程执行完特定任务后再继续。

3.3 用户线程与守护线程 ​

  • 用户线程(工作线程) :线程任务执行完毕或通过通知方式结束。

  • 守护线程:为工作线程服务。当所有用户线程结束时,守护线程自动结束。

    • 典型例子:垃圾回收线程。
    • 设置方法:线程对象.setDaemon(true); (必须在 start() 前调用)。

四、 线程的生命周期 ​

JDK中 Thread.State 枚举定义了线程的六种状态:

  1. NEW(新建) :线程对象已创建,但尚未调用 start()。
  2. RUNNABLE(可运行) :线程正在JVM中执行,或已就绪等待CPU调度。对应操作系统中的 Ready(就绪) 和 Running(运行) 状态。
  3. BLOCKED(阻塞) :线程等待获取一个监视器锁(如进入 synchronized 代码块失败)。
  4. WAITING(无限等待) :线程等待另一个线程执行特定动作(如 Object.wait())。
  5. TIMED_WAITING(计时等待) :线程在指定时间内等待(如 Thread.sleep(long))。
  6. TERMINATED(终止) :线程执行完毕,已退出。

五、 线程的同步 ​

5.1 同步的必要性 ​

在多线程环境下,当多个线程同时访问共享资源(如卖票系统的票数)时,可能导致数据不一致(超卖、重复卖)等问题。

5.2 同步机制 - Synchronized ​

保证在任一时刻,最多只有一个线程访问同步代码,确保数据完整性。

  • 同步代码块:
java
synchronized(对象) { // 得到对象的锁才能进入
    // 需要同步的代码
}
java
public synchronized void method() {
    // 需要同步的代码
}

5.3 互斥锁 ​

  • 每个Java对象都有一个互斥锁(Monitor Lock) 标记。

  • synchronized 关键字与对象的互斥锁关联。

  • 锁对象的选择:

    1. 对于非静态同步方法,锁对象默认为 this(当前对象实例)。
    2. 对于静态同步方法,锁对象默认为 当前类.class。
    3. 对于同步代码块,锁对象是 synchronized 后括号里指定的对象。
    4. 核心原则:要求多个线程竞争的锁对象必须是同一个。

5.4 释放锁与不释放锁的操作 ​

  • 会释放锁:

    1. 同步代码块/方法执行完毕。
    2. 同步代码块/方法中遇到 break、return。
    3. 同步代码块/方法中出现未处理的 Error 或 Exception。
    4. 同步代码块/方法中执行了 wait() 方法。
  • 不会释放锁:

    1. 调用 Thread.sleep() 或 Thread.yield() 暂停线程。
    2. 其他线程调用了该线程的 suspend() 方法(该方法已废弃,应避免使用)。

5.5 线程死锁 ​

  • 定义:多个线程都占用了对方所需的锁资源,并且互相等待对方释放,导致所有线程都无法继续执行。
  • 产生条件:互斥使用、不可抢占、请求和保持、循环等待。
  • 解决:在编程中应尽量避免。可以通过规定锁的获取顺序、使用 tryLock 等机制来预防。2