线程池
并行与并发
并发:强调同一时间段
并行:强调同一时刻
具体实际情况还得看CPU数量
进程和线程的关系
进程是资源分配的最小单位,线程是CPU调度的最小单位;
一个进程至少包含一个线程;
举例:一个应用软件、一个浏览器、一个SpringBoot实例就是一个进程;每一个浏览器标签页就是一个线程,每一个下载任务就是一个线程;
线程的生命周期
线程的创建时间、线程的执行时间、线程的销毁时间
线程三大主要开销
- Java 的线程模型是基于操作系统原生线程模型实现的,即是基于内核线程实现的;线程的创建,析构与同步都需要进行系统调用,在用户态和内核态切换开销大;
- 每个线程都需要一个内核线程的支持,也就会消耗一定的内核栈空间,故能创建的线程数是有限的;
- 若线程数量过多,则会导致频繁的上下文切换;
线程的状态
新建、 可运行(就绪/运行中)、 阻塞(请求获取锁)、 等待(io事件)、 定时等待(sleep)、 终止
创建线程
- 线程调度是由系统控制的,程序员不可能精准的去干涉它;
- 核心本质:new Thread()
class myThead extends Thread {
// 重写run方法
public void run() {
// do something here
}
}
public static void main(String[] args){
myThead oneThread = new myThread();
// 启动线程:自动执行run方法(手动调用run方法则没有启动新线程)
oneThread.start();
}
class SomeRunnable implements Runnable {
// 重写run方法
public void run() {
//do something here
}
}
// 创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。
Runnable oneRunnable = new SomeRunnable();
Thread oneThread = new Thread(oneRunnable);
oneThread.start();
线程池提交任务与执行任务
线程池创建
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
注
- 阻塞队列一定是要有界的,否则会导致拒绝策略失效
- 核心线程数:也称最小线程数
- 最大空闲时间:针对的是超过核心线程数之后的线程 (如果现在核心线程数为10,但当前只有3个线程,即使超过了最大空闲时间,该线程也不会被销毁)
拒绝策略
- CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
- AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
- DiscardPolicy - 直接丢弃,其他啥都没有
- DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
线程池提交任务
// 方式一:execute 方法(无返回值、无法捕获异常)
public void execute(Runnable command) {
}
// 方式二:ExecutorService 中 submit 的三个方法(有返回值、可以捕获异常)
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
Demo实战
https://www.cnblogs.com/ReturnOfTheKing/p/17989174
线程安全
背景:多线程对统一资源竞争访问,解决方案就是加锁,必须确保使用同一个资源的多个线程共用一把锁
Synchronized(JDK5之前)
- 用于锁住方法或者代码块
- 隐式加锁:自动获得锁、自动释放锁
第一种写法:
synchronized (对象){
// 需要被同步的代码
}
第二种写法:
权限修饰符 synchronized 返回值类型 方法名(形参列表){
// 需要被同步的代码
}
ReentrantLock(JDK5之后)
- 显示加锁,性能更好
- 只能锁住代码块但不能锁住方法
public class Ticket implements Runnable {
private final Lock lock = new ReentrantLock();
private int tickets = 100;
@Override
public void run() {
while (true) {
// 加锁
this.lock.lock();
try {
if (this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖第" + this.tickets-- + "张票");
}
} finally {
// 释放锁
this.lock.unlock();
}
}
}
}
volatile
注:线程在使用堆变量的时候会先将其拷贝一份到变量副本中,故可能存在数据不一致的情况。
- volatile 关键字:强制线程在每次使用的时候,都会看一下共享区域最新的值。当然也可以使用加锁的方式替代该方法。
-
volatile 关键字只能保证每次使用共享数据的时候是最新值,但是不能保证原子性。
协同关系
- wait会释放锁(Object类)、sleep不会释放锁(Thread类中声明的静态方法)
- newCondition()返回线程的阻塞队列,引入了队列模型
Async注解
参考文章
作用
使加上该注解的类或方法能够异步执行任务
参数
value参数:指定线程池
注解无效的可能情况
- 没有加@EnableAsync注解
- @Async修饰的方法不是public方法
- @Async修饰的方法被static修饰了
- 调用方法和@Async方法在同一个类中
- @Async修饰的方法的返回值不是void或Future
线程池和连接池的区别
- MySQL连接池关注于对数据库资源(数据库连接对象)进行管理,而线程池关注于对多线程资源(线程对象和CPU等计算机资源)进行管理;
参考文章
【1】https://mp.weixin.qq.com/s/smfDcR-fjSlbvbvBXp1WMA
【2】https://www.cnblogs.com/snow-flower/p/6114765.html
【3】线程池参数配置
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类