0%

线程简介

线程简介

进程与线程的核心区别

进程和线程是操作系统中两个核心的执行单元,二者的区别主要体现在资源分配和执行方式上:

对比维度 进程 线程
资源分配 操作系统分配资源的基本单位(拥有独立的堆、方法区等) 进程内的执行单元,共享进程的资源(堆、方法区),但有私有栈和程序计数器
独立性 独立运行环境,一个进程崩溃不影响其他进程 依赖进程存在,同一进程内线程崩溃可能导致整个进程崩溃
调度单位 操作系统调度资源的单位,但调度成本高 CPU 调度的基本单位,调度成本低(上下文切换开销小)
数量关系 一个进程可包含多个线程 线程是进程的一部分,不能独立存在

线程的内存结构

当 JVM 创建一个线程时,会为其分配特殊的内存区域,包含以下关键部分:

  • 程序计数器:记录线程下一条要执行的 JVM 字节码指令,确保线程在 CPU 时间片切换后能恢复执行(线程私有)。
  • Java 栈:跟踪 Java 方法的调用关系,每个方法调用对应一个栈帧,存储局部变量、参数、返回值等(线程私有)。
  • 本地方法栈:支持 native 代码的执行,与 Java 栈类似但用于本地方法调用(线程私有)。
  • 线程本地存储(Thread Local):存储线程私有变量,避免多线程共享带来的并发问题(线程私有)。
  • 状态管理变量:控制线程的生命周期(如状态标记、中断标志等)。

注意:线程的栈帧中,基本类型变量直接存储在栈上,而对象引用存储在栈上,对象本身存储在堆中(堆为进程共享资源)。

线程的创建方式

Java 提供了 4 种创建线程的方式,各有适用场景:

  1. 继承 Thread 类

    • 重写run()方法定义线程任务。
    • 启动线程:new ThreadSubClass().start()
    • 缺点:受限于 Java 单继承,灵活性低。
  2. 实现 Runnable 接口

    • 实现run()方法,通过new Thread(runnable).start()启动。
    • 优势:可多线程共享任务逻辑,避免单继承限制,代码与任务分离。
  3. 实现 Callable 接口(结合 FutureTask)

    1
    2
    FutureTask<Integer> futureTask = new FutureTask<>(call);
    new Thread(futureTask).start();
  • 实现call()方法(有返回值,可抛异常)。
  • 通过FutureTask包装任务,new Thread(futureTask).start()启动。
  • 特点:可通过futureTask.get()获取线程执行结果(会阻塞直到结果返回)。
  1. 使用线程池

    • 通过ExecutorsThreadPoolExecutor创建线程池,复用线程资源。
    • 优势:减少线程创建销毁开销,控制并发数量,适合大量短期任务。

关键:调用start()方法才会启动新线程,直接调用run()方法仅为普通方法调用,不会创建新线程。

线程的核心方法

线程的方法用于控制执行顺序、状态切换和资源同步,常用方法如下:

方法 作用
start() 启动线程,JVM 会调用run()方法(不可重复调用)。
run() 定义线程任务,直接调用时与普通方法无异。
join() 让当前线程等待目标线程执行完毕后再继续(如t1.join()让当前线程等 t1 结束)。内部通过wait(0)实现。
yield() 让当前线程让出 CPU,从运行状态转为就绪状态,重新参与 CPU 争抢(仅建议性,不一定生效)。
sleep(long ms) 让线程休眠指定时间,期间不参与 CPU 调度,不释放对象锁sleep(0)等价于yield()
wait()/notify() 用于线程间通信:wait()释放对象锁并阻塞,notify()唤醒一个等待该对象锁的线程(需在synchronized块中使用)。
interrupt() 设置线程中断标志为true,若线程处于阻塞状态(如sleepwait),会抛出InterruptedException并清除中断标志。
isInterrupted() 检查线程中断标志(实例方法,不清除标志)。
interrupted() 检查当前线程中断标志(静态方法,会清除标志)。
getState() 获取线程当前状态(如NEWRUNNABLETERMINATED等)。

重点方法对比

  • sleep() vs wait()

    | 区别点 | sleep() | wait() |
    | ———— | ——————————— | ——————————————— |
    | 所属类 | Thread(静态方法) | Object(实例方法) |
    | 锁释放 | 不释放对象锁 | 释放对象锁 |
    | 使用场景 | 任意位置(需捕获异常) | 必须在synchronized块中 |
    | 唤醒方式 | 时间到自动唤醒 | 需notify()/notifyAll()唤醒 |

线程状态及转换

线程状态变化

Java 线程的生命周期包含 6 种状态,定义在Thread.State枚举中,状态转换如下:

  1. NEW(新建):线程已创建但未调用start()
  2. RUNNABLE(可运行):包含READY(等待 CPU 调度)和RUNNING(正在执行)两种子状态。
    • 进入方式:调用start()yield()后重新争抢 CPU、从阻塞 / 等待状态恢复。
  3. BLOCKED(阻塞):等待获取synchronized锁时进入。
  4. WAITING(无限等待):需被主动唤醒才会恢复。
    • 进入方式:Object.wait()Thread.join()LockSupport.park()
    • 唤醒方式:Object.notify()Thread执行完毕(join()场景)。
  5. TIMED_WAITING(计时等待):等待指定时间后自动恢复或被提前唤醒。
    • 进入方式:Thread.sleep(ms)Object.wait(ms)Thread.join(ms)等。
  6. TERMINATED(终止):线程执行完毕(run()正常结束或异常终止)。

状态转换图:可参考原文中的示意图,核心是理解各状态间的触发条件(如锁竞争、方法调用、时间流逝等)。

其他关键概念

  1. 上下文切换
    • 定义:CPU 从一个线程切换到另一个线程时,保存当前线程状态并加载新线程状态的过程。
    • 触发时机:线程时间片用完、线程被中断、线程阻塞(如sleepwait)。
    • 开销:包括保存 / 恢复线程状态、CPU 调度时间、CPU 缓存失效等,过多切换会影响性能。
  2. 线程中断
    • 中断是一种协作机制,interrupt()仅设置中断标志,线程需主动检查标志并处理(如退出执行)。
    • 阻塞状态下的线程被中断会抛出InterruptedException,需捕获并处理(通常清除标志或退出)。
  3. 线程同步
    • 多线程共享资源时需保证原子性、可见性和有序性,常用手段:synchronized关键字、Lock接口、volatile关键字等

欢迎关注我的其它发布渠道