常见面试题之线程池

news2024/9/24 15:18:27

1. 说一下线程池的核心参数(线程池的执行原理知道嘛)?

线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数

在这里插入图片描述

  • corePoolSize 核心线程数目

  • maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)

  • keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放

  • unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等

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

  • threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等

  • handler 拒绝策略 - 当所有线程都在繁忙,workQueue也放满时,会触发拒绝策略

工作流程

在这里插入图片描述

1,任务在提交的时候,首先判断核心线程数是否已满,如果没有满则直接添加到工作线程执行

2,如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列

3,如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务

如果核心或临时线程执行完成任务后会检查阻塞队列中是否有需要执行的线程,如果有,则使用非核心线程执行任务

4,如果所有线程都在忙着(核心线程+临时线程),则走拒绝策略

拒绝策略:

  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • 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);
    }

}

2. 线程池中有哪些常见的阻塞队列?

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

比较常见的有4个,用的最多是ArrayBlockingQueueLinkedBlockingQueue

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

ArrayBlockingQueueLinkedBlockingQueue区别

LinkedBlockingQueueArrayBlockingQueue
默认无界,支持有界强制有界
底层是链表底层是数组
是懒惰的,创建节点的时候添加数据提前初始化Node 数组
入队会生成新NodeNode需要是提前创建好的
两把锁(头尾)一把锁

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

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

在这里插入图片描述

3. 如何确定核心线程数?

在设置核心线程数之前,需要先熟悉一些执行线程池执行任务的类型

  • IO密集型任务

一般来说:文件读写、DB读写、网络请求等

推荐:核心线程数大小设置为2N+1N为计算机的CPU核数)

  • CPU密集型任务

一般来说:计算型代码、Bitmap转换、Gson转换等

推荐:核心线程数大小设置为N+1N为计算机的CPU核数)

java代码查看CPU核数

在这里插入图片描述

参考回答:

① 高并发、任务执行时间短 -->CPU核数+1 ),减少线程上下文的切换

② 并发不高、任务执行时间长

  • IO密集型的任务 --> (CPU核数 * 2 + 1)

  • 计算密集型任务 -->CPU核数+1

③ 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)

4. 线程池的种类有哪些?

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

  1. 创建使用固定线程数的线程池

    在这里插入图片描述

    • 核心线程数与最大线程数一样,没有救急线程

    • 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

    • 适用场景:适用于任务量已知,相对耗时的任务

    • 案例:

      public class FixedThreadPoolCase {
      
          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)执行

    在这里插入图片描述

    • 核心线程数和最大线程数都是1

    • 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

    • 适用场景:适用于按照顺序执行的任务

    • 案例:

      public class NewSingleThreadCase {
      
          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();
          }
      
      }
      
  3. 可缓存线程池

    在这里插入图片描述

    • 核心线程数为0

    • 最大线程数是Integer.MAX_VALUE

    • 阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

    • 适用场景:适合任务数比较密集,但每个任务执行时间较短的情况

    • 案例:

      public class CachedThreadPoolCase {
      
          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 class ScheduledThreadPoolCase {
      
          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. 为什么不建议用Executors创建线程池?

参考阿里开发手册《Java开发手册-嵩山版》

在这里插入图片描述

6. 线程池使用场景CountDownLatchFuture(你们项目哪里用到了多线程)?

6.1 CountDownLatch

CountDownLatch(闭锁/倒计时锁)用来进行线程同步协作,等待所有线程完成倒计时(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行)

  • 其中构造参数用来初始化等待计数值

  • await() 用来等待计数归零

  • countDown() 用来让计数减一

在这里插入图片描述

案例代码:

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        //初始化了一个倒计时锁 参数为 3
        CountDownLatch latch = new CountDownLatch(3);

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"-begin...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //count--
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount());
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"-begin...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //count--
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount());
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"-begin...");
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //count--
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount());
        }).start();
        String name = Thread.currentThread().getName();
        System.out.println(name + "-waiting...");
        //等待其他线程完成
        latch.await();
        System.out.println(name + "-wait end...");
    }
    
}

6.2 案例一(es数据批量导入)

在我们项目上线之前,我们需要把数据库中的数据一次性的同步到es索引库中,但是当时的数据好像是1000万左右,一次性读取数据肯定不行(oom异常),当时我就想到可以使用线程池的方式导入,利用CountDownLatch来控制,就能避免一次性加载过多,防止内存溢出

整体流程就是通过CountDownLatch+线程池配合去执行

在这里插入图片描述

详细实现流程:

在这里插入图片描述

6.3 案例二(数据汇总)

在一个电商网站中,用户下单之后,需要查询数据,数据包含了三部分:订单信息、包含的商品、物流信息;这三块信息都在不同的微服务中进行实现的,我们如何完成这个业务呢?

在这里插入图片描述

  • 在实际开发的过程中,难免需要调用多个接口来汇总数据,如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能

  • 报表汇总

    在这里插入图片描述

6.4 案例三(异步调用)

在进行搜索的时候,需要保存用户的搜索记录,而搜索记录不能影响用户的正常搜索,我们通常会开启一个线程去执行历史记录的保存,在新开启的线程在执行的过程中,可以利用线程提交任务

7. 如何控制某个方法允许并发访问线程的数量?

Semaphore 信号量,是JUC包下的一个工具类,我们可以通过其限制执行的线程数量,达到限流的效果

当一个线程执行时先通过其方法进行获取许可操作,获取到许可的线程继续执行业务逻辑,当线程执行完成后进行释放许可操作,未获取达到许可的线程进行等待或者直接结束。

Semaphore两个重要的方法

lsemaphore.acquire(): 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)

lsemaphore.release():释放一个信号量,此时信号量个数+1

线程任务类:

public class SemaphoreCase {
    public static void main(String[] args) {
        // 1. 创建 semaphore 对象
        Semaphore semaphore = new Semaphore(3);
        // 2. 10个线程同时运行
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {

                try {
                    // 3. 获取许可
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println("running...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("end...");
                } finally {
                    // 4. 释放许可
                    semaphore.release();
                }
            }).start();
        }
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710424.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蘑菇车联用城市级落地讲述自动驾驶新故事

作者 | 魏启扬 来源 | 洞见新研社 “如果不能实现自动驾驶&#xff0c;特斯拉将一文不值”。 这是马斯克在接受媒体采访时的公开发言&#xff0c;这句话的语境是&#xff0c;特斯拉是自动驾驶坚实的拥护者&#xff0c;且一直在付诸行动。 可是特斯拉渐进式的单车智能路线&am…

【游戏逆向】探索可靠的线程检查方法

一、关键的线程检查 在对抗外挂和木马的方案中&#xff0c;不可能将所有的检查操作放在主线程中&#xff0c;因此&#xff0c;在方案中总有一个扫描线程或者环境检查线程必须保持工作&#xff0c;而它们也就成了外挂和木马的重要攻击目标&#xff0c;外挂和木马只要搞定了它们…

【高可用架构】聊聊故障和高可用架构设计

在架构设计中&#xff0c;高性能、高可用、可拓展以及安全等等有多种维度去判断架构的设计纬度&#xff0c;但是一般来说我们需要考虑具体的业务场景&#xff0c;去判断采用那种合适的架构方案&#xff0c;但是对于大多数的设计来说&#xff0c;都需要满足高性能、高可用。所以…

专科学历,有机会转行程序员吗?

你好&#xff0c;我是程序员晚枫&#xff0c;昨晚又1对1沟通了一位想转行程序员的朋友。 这位朋友是专科毕业&#xff0c;在电子厂干了2年多&#xff0c;感觉看不到希望&#xff0c;来找我咨询一下有没有转行程序员的可能性 学习计划。 我觉得他的情况和提问很有代表性&…

软件测试综述

概述 在不短不长的7年多研发生涯中&#xff0c;听过无数个软件测试概念&#xff1a;单元测试、功能测试、白盒测试、黑盒测试、自动化测试、契约测试、基准测试、性能测试、集成测试、渗透测试、接口测试、UI测试、端到端测试、E2E测试…… 恰逢在准备系统架构设计师软考高级…

我的创作纪念日-256天

机缘&#xff1a; 没有在csdn进行写作&#xff0c;我就不会认识更多对应领域的博主&#xff0c;并且也认识到了很多不同领域的博主这令我感到非常荣幸&#xff0c;能够加入到csdn。在分享交流的过程种我获得了&#xff1a; 实战项目中的经验分享日常学习过程中的记录通过文章进…

Ubuntu20.04安装arm-linux-gcc 4.3.2 (提供安装包)

⁡​​⁢‬‌​⁢‌⁡‍⁣⁤‬‌⁡⁢⁢‍⁡⁤​⁣‬‌&#xfeff;‬‍⁤​⁤‌⁣⁤​&#xfeff;⁤⁢‍⁢⁤‬⁣‬​‌&#xfeff;&#xfeff;Ubuntu20.04安装arm-linux-gcc 4.3.2 - 飞书云文档 (feishu.cn) CSDN编辑太丑了 转移阵地了

Google Colab:云端的Python编程神器

Google Colab&#xff0c;全名Google Colaboratory&#xff0c;是Google Research团队开发的一款云端编程工具&#xff0c;它允许任何人通过浏览器编写和执行Python代码。Colab尤其适合机器学习、数据分析和教育目的。它是一种托管式Jupyter笔记本服务&#xff0c;用户无需设置…

SQL-每日一题【185.部门工资前三高的员工】

题目 表: Employee 表: Department 公司的主管们感兴趣的是公司每个部门中谁赚的钱最多。一个部门的 高收入者 是指一个员工的工资在该部门的 不同 工资中 排名前三 。 编写一个SQL查询&#xff0c;找出每个部门中 收入高的员工 。 以 任意顺序 返回结果表。 查询结果格式如…

Python基础语法第二章、变量和运算符

目录 一、常量和表达式 二、变量和类型 2.1变量是什么 2.2变量的语法 2.2.1定义变量 2.2.2使用变量 2.3变量的类型 2.3.1整数 2.3.2浮点数(小数) 2.3.3字符串 2.3.4布尔 2.3.5其他 2.4动态类型 4.1为什么要有这么多类型? 2.4.2动态类型特性 三、注释 3.1注释…

IT人,别人公司与自家差距到底有多大?

年底&#xff0c;是IT人最心酸的时候。辛辛苦苦了一年&#xff0c;别人家的公司员工人手一台 iPhone14Pro&#xff0c;自家可能连个年会都不开&#xff01;想想就气人 不能笑饱就气饱&#xff0c;省下一顿当赚到 - 1 - 形象 别人公司的IT男帅气又有(发)型 然&#xff0c;我们…

7.3 SpringBoot整合MyBatis分页插件github.pageHelper:实现图书列表API

文章目录 前言一、自己实现分页第一步&#xff0c;count 查询 总记录数&#xff08;totalCount&#xff09;&#xff0c;计算总页数&#xff08;totalPages&#xff09;第二步&#xff0c;limit 查询 指定页数据 二、不考虑分页的查询图书列表MapperBookServiceImplBookListPar…

Redis缓存穿透击穿以及雪崩

5、Reids缓存问题与解决 5.1、背景 Redis作为一种内存性数据库&#xff0c;当查询的数据在redis缓存中时就不需要在到真实的数据库中去查&#xff0c;加快了查询速度和保护了真实数据库的安全&#xff0c;但是同时也引入了一些新的问题&#xff0c;比如查询的数据不在内存和数…

一个人也可以是【大厂】,三年程序员的生活规划心路分享!

自从工作之后&#xff0c;我就经常思考以下这些问题&#xff1a; 还有多久退休&#xff1f;明天可以退休吗&#xff1f;地球什么时候爆炸&#xff1f;我什么时候可以暴富辞职&#xff1f;我真的需要这份工作吗&#xff1f; 要问是从什么时候开始有这些问题的&#xff0c;大概…

Linux环境搭建(一)— 实现ssh连接

Linux环境搭建 一 安装虚拟机1.账号记录&#xff0c;密码root2.权限问题 二 安装ssh1.出错2.误删文件3.安装ifconfig4.安装ssh5.重装ssh6.VI环境不好用7.开放端口8.ssh文件下没有密钥文件9.无法安装ssh 三 连接ssh 写在前面&#xff1a; 使用的是VMware&#xff0c;Ubuntu环境 …

利用这个css属性,你也能轻松实现一个新手引导库

相信大家或多或少都在各种网站上使用过新手引导&#xff0c;当网站提供的功能有点复杂时&#xff0c;这是一个对新手非常友好的功能&#xff0c;可以跟随新手引导一步一步了解网站的各种功能&#xff0c;我们要做的只是点击下一步或者上一步&#xff0c;网站就能滚动到指定位置…

被中文乱码折磨的我在此总结一下编码相关知识

本文大致介绍了三个问题&#xff1a; 常见的字符编码以及他们是如何编码从而被计算机识别的&#xff1f;为什么会有这些字符编码和他们被创建的背景和顺序&#xff1f;常见的乱码问题应该如何防止以及如何解决&#xff1f; 常见的字符编码 ASCII&#xff0c;GB2312&#xff…

(小程序)按钮切换对应展示区域

(小程序)按钮切换对应展示区域 需求&#xff1a;点击按钮切换表格和图表两种展示方式 html <u-button type"primary" size"mini" text"图表" v-if"form.curType table"click"showEcharts"></u-button> <u…

同一个分支maven构建出来的包不一样?

现象 最近发布spring boot项目时遇到了一个奇怪的问题&#xff0c;日志异常信息如下&#xff1a; Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing …

Image Watch 的使用

目录 1、下载地址 2、安装完成 3、调试 1、下载地址 Image Watch - Visual Studio Marketplace 2、安装完成 打开VS&#xff0c;在项目->其他窗口中有image watch选项 3、调试 一直放大图像可以查看详细的色彩值&#xff0c;通道数为3