一、初始化线程的四种方式
1.继承Thread
2.实现Runable接口
3.实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)
方式1和方式2:主进程无法获取线程的运算结果,不合适当前场景。
方式3:主进程可以获取线程的运算结果,但是不利于服务器种的线程资源,会导致服务器资源耗尽
方式4:正常开发通过线程池的方式初始化线程,因为通过线程池性能稳定,也可以获取执行结果,并捕获异常。
Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory,
二、线程池的七大参数
1、核心线程数:线程池中一直保持的可运行的核心线程。
2、最大线程数:线程池中允许的最大线程数。
3.空闲线程时间:当线程池中的线程数量超过核心线程数,且这些线程空闲时间超过空闲线程时间,多余的线程就会销毁,直到线程池中的线程数量不大于核心线程数。
4.时间单位:线程的时间单位
5.阻塞队列:用来存储等待执行的任务线程,如果当前对线程的需求超过了核心线程数大小,就会放在这里等待空闲线程执行。
6.工厂:创建线程的工厂
7.拒绝策略:当队列满,且线程达到最大线程后,还有任务进来,线程池就会使用拒绝策略。
三、线程池的运行流程:
例子:一个线程池 核心线程数10个,最大线程数20个,阻塞队列50个、共100个并发任务进来如何分配:
10个核心线程直接执行,然后50个任务进入阻塞队列,因为最大线程是20,最大线程-核心线程=10个线程,还可以安排10个线程,最大可以安排70个线程执行(阻塞队列数+最大线程数),剩余30个任务进入拒绝策略处理,不执行。
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个线程池,core 10:max20,queue:50
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // 核心线程数为 10
20, // 最大线程数为 20
0L, TimeUnit.MILLISECONDS, // keep-alive time 为 0 毫秒,即非核心线程空闲立即回收
new LinkedBlockingQueue<>(50)); // 阻塞队列大小为 50
// 设置拒绝策略为抛出异常
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
try {
// 开始提交 100 个任务
for (int i = 0; i < 100; i++) {
executor.execute(() -> {
try {
Thread.sleep(100);
System.out.println("Task executed by thread " + Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} finally {
// 关闭线程池
executor.shutdown();
}
}
}
三、开发中为什么要使用线程池?
1、降低资源的消耗:
通过复用线程池中创建好的线程,降低线程重复创建和销毁造成的损耗。
2、提高响应速度:
因为线程池中的线程数没有超过线程池的最大上限时,有点线程处于等待分配任务的状态,当任务来的时候无需创建新的线程就可以复用执行
3、提高线程的可管理性
线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来
的系统开销,无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使
用线程池可以进行线程统一分配。
四、常用的线程池有哪些?
1、定时任务线程池(ScheduledThreadPool):该线程池主要用于周期性的任务,它可以按照指定的时间间隔,或者指定的时间执行任务,可以通过AOP+@Log用来异步执行操作日志插入等操作。
2、单例线程池(SingleThreadExecutor):该线程池中只有一个线程,用于顺序执行线程任务。
3、自定义线程池(ThreadPoolExecutor):在编程中,使用线程池的概念创建一个能够重复利用线程的对象池。自定义线程池可以控制同时运行线程数量,以达到最优的性能和资源利用率。