前言
对于一名 Java 开发者,线程是一个绕不开的话题。尽管线程能有效利用系统资源,提高系统的性能,但在使用过程中也伴随不少的问题。
今天,『知行』就为大家梳理一下,Java 线程中必须要掌握的知识点。
线程的创建方式
在 Java 中,线程的创建方式通常有 4 种方式:
- 继承 Thread 类,重写 run() 方法,这种方式受限 Java 的单继承,灵活性较差;
- 实现 Runnable 接口,这种方式可避免继承限制,比较灵活;
- 实现 Callable 接口,支持获取返回值;
- 线程池,能够管理线程生命周期;
线程的生命周期
Java 线程的生命周期有 6 个阶段:
- New(新建):创建后尚未启动;
- Runnable(可运行):调用
start()
后,等待CPU
时间片; - Blocked(阻塞):等待锁或
I/O
资源(如synchronized
); - Waiting(等待):主动调用
wait()
或join()
进入等待; - Timed_Waiting(超时等待):带超时的
sleep()
或wait(timeout)
; - Terminated(终止);
run()
执行完毕或异常终止;
死锁的必要条件
如果发生了死锁,必定会满足以下 4 个必要条件(缺一不可):
- 互斥条件:资源是独占的,同一时间只能被一个进程(或线程)使用;
- 请求与保持条件:当一个线程持有至少一个资源的同时,又请求其他线程持有的资源,且不释放已占用的资源。
- 不可剥夺条件:线程已获得的资源,不能被其他线程强行剥夺,只能由持有者主动释放。
- 循环等待条件:每个线程都在等待另一个线程占用的资源,形成了一个循环等待链。
当发生死锁时,破坏任意一个条件即可避免死锁。例如:
- 破坏互斥条件(通常破坏不了):允许资源共享;
- 破坏请求与保持条件:线程一次性申请所有资源;
- 破坏不可剥夺条件(通常破坏不了):允许系统强制剥夺资源;
- 破坏循环等待条件:按顺序申请资源;
线程池运行原理
线程池的工作流程主要分为以下几个阶段:
- 任务提交,核心线程未满,创建新线程执行;
- 核心线程已满,则将任务放入任务队列;
- 任务队列已满,则创建非核心线程执行(直至最大线程数);
- 线程和任务队列均满,则会触发拒绝策略;
线程池生命周期
线程池生命周期有 5 种状态:
- RUNNING(运行状态):接收新任务并处理队列中的任务;
- SHUTDOWN(关闭状态):不接收新任务,但会处理队列中的剩余任务;
- STOP(停止状态):不接收新任务,不处理队列任务,并尝试中断正在执行的任务;
- TIDYING(整理状态):所有任务已终止,工作线程数为
0
; - TERMINATED(终止状态):
terminated()
方法执行完毕,线程池完全关闭;
线程池的核心参数
线程池的核心参数共有 7 个:
- corePoolSize(核心线程数):即使空闲也不会被回收的线程数量;
- maximumPoolSize(最大线程数):线程池允许创建的最大线程数(含核心线程);
- keepAliveTime(空闲线程存活时间):非核心线程空闲时的存活时间(超时后回收);
- unit(时间单位):keepAliveTime 的时间单位(如秒、毫秒);
- workQueue(任务队列):用于缓存未执行任务的阻塞队列;
- ThreadFactory(线程工厂):自定义线程创建方式;
- RejectedExecutionHandler(拒绝策略):当任务队列满且线程数达到上限时的处理策略;
线程池的拒绝策略
Java 线程池提供了 4 种拒绝策略:
- CallerRunsPolicy:由提交任务的线程直接执行该任务;
- AbortPolicy(默认):抛出
RejectedExecutionException
异常,中断任务提交; - DiscardPolicy:静默丢弃新任务,不抛异常也不执行;
- DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新提交当前任务;
知行有话
本期的分享到此结束啦!如果大家认为 Java 线程中还有哪些比较核心的知识点没有提及到,欢迎在评论区留言哦~