线程池
核心概念
并行与并发
并发:强调同一时间段,交替执行
并行:强调同一时刻
具体实际情况还需要看CPU数量
进程和线程的关系
进程是资源分配的最小单位,线程是CPU调度的最小单位;一个进程至少包含一个线程;
举例:一个应用软件、一个浏览器、一个SpringBoot实例就是一个进程;每一个浏览器标签页就是一个线程,每一个下载任务就是一个线程;
springboot中main函数启动主线程,它可以创建其他子线程,包括tomcat的监听线程和工作线程,所以每一个HTTP请求对应的是一个独立的线程;
线程的生命周期
线程的创建时间、线程的执行时间、线程的销毁时间
线程三大主要开销
- Java 的线程模型是基于操作系统原生线程模型实现的,即是基于内核线程实现的;线程的创建,析构与同步都需要进行系统调用,在用户态和内核态切换开销大;
- 每个线程都需要一个内核线程的支持,也就会消耗一定的内核栈空间,故能创建的线程数是有限的;
- 若线程数量过多,则会导致频繁的上下文切换;
线程管理
线程的状态
新建、 可运行(就绪/运行中)、 阻塞(请求获取锁)、 等待(io事件)、 定时等待(sleep)、 终止

创建线程
- 线程调度是由系统控制的,程序员不可能精准的去干涉它,即看CPU心情;
- 核心本质: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);
代码实战
https://www.cnblogs.com/ReturnOfTheKing/p/17989174
线程安全
背景:多线程对同一资源(同一个对象)竞争访问,解决方案就是加锁,必须确保使用同一个资源的多个线程共用一把锁
@RestController
public class DemoController {
private int count = 0; // 线程不安全
@GetMapping("/inc")
public int inc() {
return ++count;
}
}
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】线程池参数配置

浙公网安备 33010602011771号