🍊一. 认识线程池
关于“池”的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取
我们之前使用多线程的时候都会创建线程和销毁线程,但是创建和销毁都会耗费大量资源,所以线程池的作用就是减小创建和销毁时的损耗
🍬举例形象说明线程池:
我们假设线程池为一个快递公司,里面有正式员工,但是在双11,双12因为快递量大所以有临时工,但是高峰期过后需要解雇临时工,快递都放在仓库,当仓库满了之后就不在接收快递,快递员送快递是从仓库拿快递然后送
🍉二. 原生线程池(ThreadPoolExecutor)
ThreadPoolExecutor提供了更多的参数,可以进一步细化线程池的行为
🍬ThreadPoolExecutor的构造方法:
🍬构造方法参数解析:对应上述例子来结合理解
🍃corePoolSize,核心线程数:正式员工
🍃maximumPoolSize,最大线程数:正式员工和临时员工总数
🍃keepAliveTime,空闲时间:临时工空闲(keepAliveTime,TimeUnit结合来决定何时解雇临时工)
🍃TimeUnit,空闲时间单位:空闲时间(keepAliveTime,TimeUnit结合来决定何时解雇临时工)
🍃workQueue,阻塞队列:快递仓库
🍃threadFactory:使用工厂对象提供的方法来创建线程(了解)
🍃RejectedExecutionHandler,拒绝策略:仓库满了不再接收快递
🍬拒绝策略:
🍂AbortPolicy(): 以抛出异常的方式拒绝(默认的拒绝策略)
🍂CallerRunsPolicy(): 让调用的线程来处理
🍂DiscardOldestPolicy(): 丢弃时间最久的任务(先进先出)
🍂DiscardPolicy(): 丢弃新来的任务
👁🗨️实现代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//使用原生api来创建(ThreadPoolExecutor)
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //线程核心数
10, //最大线程数
60, //空闲时间
TimeUnit.SECONDS, //空闲时间单位
new ArrayBlockingQueue(100), //阻塞队列
new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略,抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() 让调用线程自己处理
// new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃时间最久任务
// new ThreadPoolExecutor.DiscardPolicy() 丢弃新来的任务
);
//提交任务使用:submit/execute
for(int i = 0;i < 10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
🍏三. ExecutorService和 Executors创建线程池
这种创建方式对应就是ThreadPoolExecutor构造参数中的threadFactory参数,使用工厂对象提供的创建方式
ExecutorService 表示一个线程池实例
Executors 是一个工厂类, 能够创建出几种不同风格的线程池
提交任务到线程池中的阻塞队列中:
🍁execute(Runnable task)
🍁submit(Runnable task)
🍁submit(Callable task)
🍬Executors创建线程池的几种方式:
☘️newFixedThreadPool:创建固定线程数的线程池
☘️newCachedThreadPool:创建线程数目动态增长的线程池
☘️newSingleThreadExecutor:创建只包含单个线程的线程池
☘️newScheduledThreadPool:创建有计划任务的线程池(带有定时器功能)
👁🗨️示例代码:
//创建有缓存的线程池
ExecutorService pool1 = Executors.newCachedThreadPool();
//创建有固定大小的线程池
ExecutorService pool2 = Executors.newFixedThreadPool(4);
//创建有计划任务的线程池
ExecutorService pool3 = Executors.newScheduledThreadPool(4);
//创建只有单个线程的线程池
ExecutorService pool4 = Executors.newSingleThreadExecutor();
👁🗨️注意:这种创建的方式在以后的工作中不建议用
🍂在工作中要阻塞队列设置大小,如果不设置大小,在某个时间会导致内存不够,出现OOM(内存溢出)
🍂拒绝策略要自己扩展实现(比如任务记录在日志或者数据库里)
🫐四. 线程池的工作流程
结合上述快递公司例子说明:
🍋五. 线程池的模拟实现
前提说明:
🍁这里阻塞队列的实现采用链表的方式阻塞
🍁我们要求线程池创建的时候,就创建线程不停的从队列中取任务来执行
🍁这里创建5个员工
👁🗨️代码实现:
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MyThreadPool {
private BlockingDeque queue = new LinkedBlockingDeque();
//员工的数量
public MyThreadPool(int num){
for(int i = 0;i < num;i++){
//线程池创建的时候,就创建线程不停的从队列中取任务来执行
new Thread(new Runnable() {
@Override
public void run() {
while(true){ //不停的取任务
try {
Runnable task = queue.take(); //获取任务
task.run(); //执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
//放任务
public void execute(Runnable task){
try {
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(5);
while(true){
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
Thread.sleep(1000);
}
}
}
部分打印结果: