首页 > 其他分享 >线程池的执行原理

线程池的执行原理

时间:2023-12-13 23:31:53浏览次数:41  
标签:队列 threadPool 任务 线程 new 原理 执行 public

1.线程池的核心参数

线程池七大核心参数如下所示:

public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler
        )
  • corePoolSize: 核心线程数目
  • maximumPoolSize最大线程数目 = (核心线程+救急线程的最大数目)
  • keepAliveTime生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
  • unit时间单位 , 救急线程的生存时间单位,如秒、毫秒等
  • workQueue: 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  • **threadFactory: 线程工厂 , 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  • handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

思考:线程池的执行原理知道嘛?

2. 线程池的执行原理知道嘛

  • 1: 任务在提交的时候,首先判断核心线程数是否已满,如果没有满则直接添加到工作线程执行
  • 2: 如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列
  • 3:如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务如果核心或临时线程执行完成任务后会检查阻塞队列中是否有需要执行的线程,如果有,则使用非核心线程执行任务
  • 4:如果所有线程都在忙着(核心线程+临时线程),则走拒绝策略

线程池的执行原理_阻塞队列

思考:拒绝策略有哪些?

  • 1.AbortPolicy:直接抛出异常,默认策略
  • 2.CallerRunsPolicy:用调用者所在的线程来执行任务
  • 3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
  • 4.DiscardPolicy:直接丢弃任务

案例:

public class TestThreadPoolExecutor {

    static class MyTask implements Runnable {
        private final String name;
        private final long duration;

        public MyTask(String name) {
            this(name, 0);
        }

        public MyTask(String name, long duration) {
            this.name = name;
            this.duration = duration;
        }

        @Override
        public void run() {
            try {
                LoggerUtils.get("myThread").debug("running..." + this);
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "MyTask(" + name + ")";
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger c = new AtomicInteger(1);
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                queue,
                r -> new Thread(r, "myThread" + c.getAndIncrement()),
                new ThreadPoolExecutor.AbortPolicy());
        showState(queue, threadPool);
        threadPool.submit(new MyTask("1", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("2", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("3"));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("4"));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("5",3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("6"));
        showState(queue, threadPool);
    }

    private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) {
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        List<Object> tasks = new ArrayList<>();
        for (Runnable runnable : queue) {
            try {
                Field callable = FutureTask.class.getDeclaredField("callable");
                callable.setAccessible(true);
                Object adapter = callable.get(runnable);
                Class<?> clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter");
                Field task = clazz.getDeclaredField("task");
                task.setAccessible(true);
                Object o = task.get(adapter);
                tasks.add(o);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        LoggerUtils.main.debug("pool size: {}, queue: {}", threadPool.getPoolSize(), tasks);
    }

}

思考:线程池中有哪些常见的阻塞队列?

3. 常见阻塞队列

workQueue: 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务

比较常见的有4个,用的最多是ArrayBlockingQueue和LinkedBlockingQueue

  • 1.ArrayBlockingQueue:基于数组结构的有界阻塞队列
  • 2.LinkedBlockingQueue:基于链表结构的有界阻塞队列
  • 3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的
  • 4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作

3.1. ArrayBlockingQueue的LinkedBlockingQueue区别

inkedBlockingQueue**

ArrayBlockingQueue

默认无界,支持有界

强制有界

底层是链表

底层是数组

是懒惰的,创建节点的时候添加数据

提前初始化 Node  数组

入队会生成新 Node

Node需要是提前创建好的

两把锁(头尾)

一把锁

左边是LinkedBlockingQueue加锁的方式,右边是ArrayBlockingQueue加锁的方式

线程池的执行原理_阻塞队列_02

  • LinkedBlockingQueue:读和写各有一把锁,性能相对较好
  • ArrayBlockingQueue:只有一把锁,读和写公用,性能相对于LinkedBlockingQueue差一些

4. 线程池的种类有哪些

在java.util.concurrent.Executors类中提供了大量创建连接池的静态方法,常见就有四种

  1. 创建使用固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, 
                                      0L, 
                                      TimeUnit.MILLISECONDS, 
                                      new LinkedBlockingQueue<>());
    }
  • 核心线程数与最大线程数一样,没有救急线程
  • 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE
  • 适用场景:适用于任务量已知,相对耗时的任务
  • 案例
/**
 * @author maguobin
 * @description: TODO
 */
public class FixedThreadPoolTest {

    static class FixedThreadDemo implements Runnable{
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            for (int i = 0; i < 2; i++) {
                System.out.println(name + ":" + i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //创建一个固定大小的线程池,核心线程数和最大线程数都是3
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            executorService.submit(new FixedThreadDemo());
            Thread.sleep(10);
        }

        executorService.shutdown();
    }

}

2.单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 
        1, 
        0L,
        TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue<>()));
    }
  • 核心线程数和最大线程数都是1
  • 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE
  • 适用场景:适用于按照顺序执行的任务
  • 案例
/**
 * @author maguobin
 * @description: TODO
 */
public class NewSingleThreadTest {
    static int count = 0;

    static class Demo implements Runnable {
        @Override
        public void run() {
            count++;
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //单个线程池,核心线程数和最大线程数都是1
        ExecutorService exec = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            exec.execute(new Demo());
            Thread.sleep(5);
        }
        exec.shutdown();
    }
}
  1. 可缓存线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0,
                                    Integer.MAX_VALUE, 
                                    60L,
                                    TimeUnit.SECONDS, n
                                    ew SynchronousQueue<>());
    }
  • 核心线程数为0
  • 最大线程数是Integer.MAX_VALUE
  • 阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作
  • 适用场景:适合任务数比较密集,但每个任务执行时间较短的情况
  • 案例:
/**
 * @author maguobin
 * @description: TODO
 */
public class CachedThreadPoolTest {
    static class Demo implements Runnable {
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            try {
                //修改睡眠时间,模拟线程执行需要花费的时间
                Thread.sleep(100);

                System.out.println(name + "执行完了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //创建一个缓存的线程,没有核心线程数,最大线程数为Integer.MAX_VALUE
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            exec.execute(new Demo());
            Thread.sleep(1);
        }
        exec.shutdown();
    }
}

4.提供了“延迟”和“周期执行”功能的ThreadPoolExecutor

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler{
 super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler);                     
}
  • 适用场景:有定时和延迟执行的任务
  • 案例
/**
 * @author maguobin
 * @description: TODO
 */
public class ScheduledThreadPoolTest {
    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                String name = Thread.currentThread().getName();

                System.out.println(name + ", 开始:" + new Date());
                Thread.sleep(1000);
                System.out.println(name + ", 结束:" + new Date());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //按照周期执行的线程池,核心线程数为2,最大线程数为Integer.MAX_VALUE
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
        System.out.println("程序开始:" + new Date());

        /**
         * schedule 提交任务到线程池中
         * 第一个参数:提交的任务
         * 第二个参数:任务执行的延迟时间
         * 第三个参数:时间单位
         */
        scheduledThreadPool.schedule(new Task(), 0, TimeUnit.SECONDS);
        scheduledThreadPool.schedule(new Task(), 1, TimeUnit.SECONDS);
        scheduledThreadPool.schedule(new Task(), 5, TimeUnit.SECONDS);

        Thread.sleep(5000);

        // 关闭线程池
        scheduledThreadPool.shutdown();

    }
}

5. 线程池面试题

面试官:线程池的核心参数有哪些?

候选人

在线程池中一共有7个核心参数:

  1. corePoolSize 核心线程数目 - 池中会保留的最多线程数
  2. maximumPoolSize 最大线程数目 - 核心线程+救急线程的最大数目
  3. keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
  4. unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
  5. workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  6. threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  7. handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

在拒绝策略中又有4中拒绝策略

  • 第一种是AbortPolicy,之际抛异常
  • 第二种是CallerRunsPolicy由调用者执行任务
  • 第三是DiscardOldestPolicy丢弃当前的任务
  • 第四是DiscardPolicy丢弃最早排队任务。默认是直接抛异常。

面试官:线程池的执行原理知道吗?

候选人

首先判断线程池里的核心线程是否都在执行任务,如果不是则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队 列里。如果工作队列满了,则判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任 务。如果已经满了,则交给拒绝策略来处理这个任务。

面试官:线程池的种类有哪些?

候选人

在jdk中默认提供了4中方式创建线程池

第一个是:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回 收空闲线程,若无可回收,则新建线程。

第二个是:newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列 中等待。

第三个是:newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

第四个是:newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

标签:队列,threadPool,任务,线程,new,原理,执行,public
From: https://blog.51cto.com/maguobin/8807917

相关文章

  • Vite原理
    当前工程化的痛点在浏览器支持ESModule之前,JavaScript并没有提供原生机制让开发者以模块化的方式进行开发。这也是打包工具诞生的原因:使用工具抓取,处理并将源码模块串联成可以在浏览器中运行的文件。虽然现在有webpack,Rollup等工具,极大地改善了前端开发者的体验。但是当构建的......
  • ThreadLocal原理
    ThreadLocal主要起到线程隔离作用,使得每个线程拥有自己独立的一份数据,经过threadLocal处理的数据是线程独享的,不与其它线程分享或者干扰,因此能起到线程之间数据隔离的作用。ThreadLocal的几个核心方法:方法声明描述publicvoidset(Tvalue)设置当前线程绑定的局部变量pu......
  • UBUNTU 18.04.6 在编译LINUX内核的时候执行MAKE ARCH=ARM SOCFPGA_DEFCONFIG提示Can't
     Intel针对SoCFPGA芯片提供的Linux源码中已经提供好了一个名为socfpga_defconfig的配置文件,我们对内核的配置和修改,建议基于此配置文件进行,因此在进行配置前,需要先将该配置文件导入到默认配置文件.config中,操作方法很简单。 在终端输入makeARCH=armsocfpga_defconfig......
  • UBUNTU 18.04.6 在编译linux内核的时候执行make ARCH=arm socfpga_defconfig设置默认
    在编译linux内核的时候执行makeARCH=armsocfpga_defconfig设置默认配置时报错bisonflexnotfound缺少文件:/bin/sh:1:bison:notfound 输入命令sudoapt-getinstallbison进行安装: /bin/sh:1:flex:notfound 输入命令 sudoapt-getinstallflex进行安......
  • MySQL 执行一条查询语句的内部执行过程?
    客户端先通过连接器连接到MySQL服务器。连接器权限验证通过之后,先查询是否有查询缓存,如果有缓存(之前执行过此语句)则直接返回缓存数据,如果没有缓存则进入分析器。分析器会对查询语句进行语法分析和词法分析,判断SQL语法是否正确,如果查询语法错误会直接返回给客户端错误信息,如果语......
  • MySQL 提示“不存在此列”是执行到哪个节点报出的?
    在MySQL中,当执行一条查询语句时,如果出现"不存在此列"的错误提示,通常是在查询解析阶段报出的。查询解析是MySQL执行查询语句的第一个阶段,它负责对查询语句进行语法解析和语义解析。在这个阶段,MySQL会检查查询语句中的表、列、函数等是否存在,并验证其正确性。如果查询语句中引用了不......
  • 理解Mysql索引原理及特性
    作为开发人员,碰到了执行时间较长的sql时,基本上大家都会说”加个索引吧”。但是索引是什么东西,索引有哪些特性,下面和大家简单讨论一下。1索引如何工作,是如何加快查询速度索引就好比书本的目录,提高数据库表数据访问速度的数据库对象。当我们的请求打过来之后,如果有目录,就会快速的......
  • Python学习多线程、多进程、多协程记录
    一、多线程应用于请求和IO#1.Python中关于使用多线程多进程的库/模块#2.选择并发编程方式(多线程Thread、多进程Process、多协程Coroutine)前置知识: 一、三种有各自的应用场景 1.一个进程中可以启动多个线程 2.一个线程中可以启动多个协程 二、各自优缺点 1......
  • 视频流的含义、定义及其工作原理分析
    流媒体是一种通过互联网传输,将音频、视频等多媒体内容从存储设备传输到另一个设备的技术。与传统下载方式不同,流媒体可以实现边下边播,用户无需等待完整文件下载即可开始观看,同时具有流畅体验。流媒体的优点在于方便快捷,用户只需要网络连接和播放设备就能随时随地观看或听取所需内......
  • Java并发(十八)----常见线程安全类及实例分析
    1、常见线程安全类StringIntegerStringBufferRandomVectorHashtablejava.util.concurrent(JUC)包下的类这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。Hashtabletable=newHashtable();​newThread(()->{  ......