🍊一. 认识线程池
关于“池”的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取
我们之前使用多线程的时候都会创建线程和销毁线程,但是创建和销毁都会耗费大量资源,所以线程池的作用就是减小创建和销毁时的损耗
🍬举例形象说明线程池:
我们假设线程池为一个快递公司,里面有正式员工,但是在双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); } } }
部分打印结果: