线程池(面试常考)

简介: 关于“池”的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取。

🍊一. 认识线程池

关于“池”的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取。


我们之前使用多线程的时候都会创建线程和销毁线程,但是创建和销毁都会耗费大量资源,所以线程池的作用就是减小创建和销毁时的损耗


🍬举例形象说明线程池:


我们假设线程池为一个快递公司,里面有正式员工,但是在双11,双12因为快递量大所以有临时工,但是高峰期过后需要解雇临时工,快递都放在仓库,当仓库满了之后就不在接收快递,快递员送快递是从仓库拿快递然后送


🍉二. 原生线程池(ThreadPoolExecutor)

ThreadPoolExecutor提供了更多的参数,可以进一步细化线程池的行为


🍬ThreadPoolExecutor的构造方法:

微信图片_20221029144449.jpg

🍬构造方法参数解析:对应上述例子来结合理解


🍃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创建线程池的几种方式:i


☘️newFixedThreadPool:创建固定线程数的线程池

☘️newCachedThreadPool:创建线程数目动态增长的线程池

☘️newSingleThreadExecutor:创建只包含单个线程的线程池

☘️newScheduledThreadPool:创建有计划任务的线程池(带有定时器功能)


👁‍🗨️示例代码:

//创建有缓存的线程池
        ExecutorService pool1 = Executors.newCachedThreadPool();
        //创建有固定大小的线程池
        ExecutorService pool2 = Executors.newFixedThreadPool(4);
        //创建有计划任务的线程池
        ExecutorService pool3 = Executors.newScheduledThreadPool(4);
        //创建只有单个线程的线程池
        ExecutorService pool4 = Executors.newSingleThreadExecutor();

   

👁‍🗨️注意:这种创建的方式在以后的工作中不建议用


🍂在工作中要阻塞队列设置大小,如果不设置大小,在某个时间会导致内存不够,出现OOM(内存溢出)

🍂拒绝策略要自己扩展实现(比如任务记录在日志或者数据库里)


🫐四. 线程池的工作流程

结合上述快递公司例子说明:

image.png



🍋五. 线程池的模拟实现

前提说明:


🍁这里阻塞队列的实现采用链表的方式阻塞

🍁我们要求线程池创建的时候,就创建线程不停的从队列中取任务来执行

🍁这里创建5个员工


👁‍🗨️代码实现:

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MyThreadPool {
    private BlockingDeque<Runnable> 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);
        }
    }
}


部分打印结果:

微信图片_20221029144628.jpg

相关文章
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
70 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
2月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
47 2
面试官:说说停止线程池的执行流程?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
31 1
面试官:如何实现线程池任务编排?
|
3月前
|
Java
【多线程面试题二十五】、说说你对AQS的理解
这篇文章阐述了对Java中的AbstractQueuedSynchronizer(AQS)的理解,AQS是一个用于构建锁和其他同步组件的框架,它通过维护同步状态和FIFO等待队列,以及线程的阻塞与唤醒机制,来实现同步器的高效管理,并且可以通过实现特定的方法来自定义同步组件的行为。
【多线程面试题二十五】、说说你对AQS的理解
|
3月前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
45 2
|
3月前
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
3月前
|
安全 Java
【多线程面试题十九】、 公平锁与非公平锁是怎么实现的?
这篇文章解释了Java中`ReentrantLock`的公平锁和非公平锁的实现原理,其中公平锁通过检查等待队列严格按顺序获取锁,而非公平锁允许新线程有更高机会立即获取锁,两者都依赖于`AbstractQueuedSynchronizer`(AQS)和`volatile`关键字以及CAS技术来确保线程安全和锁的正确同步。
【多线程面试题十九】、 公平锁与非公平锁是怎么实现的?
|
3月前
|
存储 缓存 安全
Java多线程面试题总结(中)
Java内存模型(JMM)定义了程序中所有变量的访问规则与范围,确保多线程环境下的数据一致性。JMM包含主内存与工作内存的概念,通过8种操作管理两者间的交互,确保原子性、可见性和有序性。`synchronized`和`volatile`关键字提供同步机制,前者确保互斥访问,后者保证变量更新的可见性。多线程操作涉及不同状态,如新建(NEW)、可运行(RUNNABLE)等,并可通过中断、等待和通知等机制协调线程活动。`volatile`虽不确保线程安全,但能确保变量更新对所有线程可见。
19 0
|
3月前
|
Java 程序员 容器
【多线程面试题二十四】、 说说你对JUC的了解
这篇文章介绍了Java并发包java.util.concurrent(简称JUC),它是JSR 166规范的实现,提供了并发编程所需的基础组件,包括原子更新类、锁与条件变量、线程池、阻塞队列、并发容器和同步器等多种工具。