Java 线程必须了解的哪些事儿

158 阅读4分钟

前言

对于一名 Java 开发者,线程是一个绕不开的话题。尽管线程能有效利用系统资源,提高系统的性能,但在使用过程中也伴随不少的问题。

今天,『知行』就为大家梳理一下,Java 线程中必须要掌握的知识点。

线程的创建方式

在 Java 中,线程的创建方式通常有 4 种方式:

  1. 继承 Thread 类,重写 run() 方法,这种方式受限 Java 的单继承,灵活性较差;
  2. 实现 Runnable 接口,这种方式可避免继承限制,比较灵活;
  3. 实现 Callable 接口,支持获取返回值;
  4. 线程池,能够管理线程生命周期;

线程的生命周期

Java 线程的生命周期有 6 个阶段:

  1. New(新建):创建后尚未启动;
  2. Runnable(可运行):调用 start() 后,等待 CPU 时间片;
  3. Blocked(阻塞):等待锁或 I/O 资源(如 synchronized);
  4. Waiting(等待):主动调用 wait()join() 进入等待;
  5. Timed_Waiting(超时等待):带超时的 sleep()wait(timeout)
  6. Terminated(终止);run() 执行完毕或异常终止;

死锁的必要条件

如果发生了死锁,必定会满足以下 4 个必要条件(缺一不可):

  1. 互斥条件:资源是独占的,同一时间只能被一个进程(或线程)使用;
  2. 请求与保持条件:当一个线程持有至少一个资源的同时,又请求其他线程持有的资源,且不释放已占用的资源。
  3. 不可剥夺条件:线程已获得的资源,不能被其他线程强行剥夺,只能由持有者主动释放。
  4. 循环等待条件:每个线程都在等待另一个线程占用的资源,形成了一个循环等待链。

当发生死锁时,破坏任意一个条件即可避免死锁。例如:

  1. 破坏互斥条件(通常破坏不了):允许资源共享;
  2. 破坏请求与保持条件:线程一次性申请所有资源;
  3. 破坏不可剥夺条件(通常破坏不了):允许系统强制剥夺资源;
  4. 破坏循环等待条件:按顺序申请资源;

线程池运行原理

线程池的工作流程主要分为以下几个阶段:

  1. 任务提交,核心线程未满,创建新线程执行;
  2. 核心线程已满,则将任务放入任务队列;
  3. 任务队列已满,则创建非核心线程执行(直至最大线程数);
  4. 线程和任务队列均满,则会触发拒绝策略;

线程池生命周期

线程池生命周期有 5 种状态:

  1. RUNNING(运行状态):接收新任务并处理队列中的任务;
  2. SHUTDOWN(关闭状态):不接收新任务,但会处理队列中的剩余任务;
  3. STOP(停止状态):不接收新任务,不处理队列任务,并尝试中断正在执行的任务;
  4. TIDYING(整理状态):所有任务已终止,工作线程数为 0
  5. TERMINATED(终止状态):terminated() 方法执行完毕,线程池完全关闭;

线程池的核心参数

线程池的核心参数共有 7 个:

  1. corePoolSize(核心线程数):即使空闲也不会被回收的线程数量;
  2. maximumPoolSize(最大线程数):线程池允许创建的最大线程数(含核心线程);
  3. keepAliveTime(空闲线程存活时间):非核心线程空闲时的存活时间(超时后回收);
  4. unit(时间单位):keepAliveTime 的时间单位(如秒、毫秒);
  5. workQueue(任务队列):用于缓存未执行任务的阻塞队列;
  6. ThreadFactory(线程工厂):自定义线程创建方式;
  7. RejectedExecutionHandler(拒绝策略):当任务队列满且线程数达到上限时的处理策略;

线程池的拒绝策略

Java 线程池提供了 4 种拒绝策略:

  1. CallerRunsPolicy:由提交任务的线程直接执行该任务;
  2. AbortPolicy(默认):抛出 RejectedExecutionException 异常,中断任务提交;
  3. DiscardPolicy:静默丢弃新任务,不抛异常也不执行;
  4. DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新提交当前任务;

知行有话

本期的分享到此结束啦!如果大家认为 Java 线程中还有哪些比较核心的知识点没有提及到,欢迎在评论区留言哦~