当前位置:   article > 正文

Java面试高频题汇总:线程池相关内容

Java面试高频题汇总:线程池相关内容

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

难易程度:☆☆☆
出现频率:☆☆☆☆

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

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

工作流程

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

拒绝策略:

1.AbortPolicy:直接抛出异常,默认策略;

2.CallerRunsPolicy:用调用者所在的线程来执行任务;

3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

4.DiscardPolicy:直接丢弃任务;

参考代码:

  1. public class TestThreadPoolExecutor {
  2. static class MyTask implements Runnable {
  3. private final String name;
  4. private final long duration;
  5. public MyTask(String name) {
  6. this(name, 0);
  7. }
  8. public MyTask(String name, long duration) {
  9. this.name = name;
  10. this.duration = duration;
  11. }
  12. @Override
  13. public void run() {
  14. try {
  15. LoggerUtils.get("myThread").debug("running..." + this);
  16. Thread.sleep(duration);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. @Override
  22. public String toString() {
  23. return "MyTask(" + name + ")";
  24. }
  25. }
  26. public static void main(String[] args) throws InterruptedException {
  27. AtomicInteger c = new AtomicInteger(1);
  28. ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
  29. ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
  30. 2,
  31. 3,
  32. 0,
  33. TimeUnit.MILLISECONDS,
  34. queue,
  35. r -> new Thread(r, "myThread" + c.getAndIncrement()),
  36. new ThreadPoolExecutor.AbortPolicy());
  37. showState(queue, threadPool);
  38. threadPool.submit(new MyTask("1", 3600000));
  39. showState(queue, threadPool);
  40. threadPool.submit(new MyTask("2", 3600000));
  41. showState(queue, threadPool);
  42. threadPool.submit(new MyTask("3"));
  43. showState(queue, threadPool);
  44. threadPool.submit(new MyTask("4"));
  45. showState(queue, threadPool);
  46. threadPool.submit(new MyTask("5",3600000));
  47. showState(queue, threadPool);
  48. threadPool.submit(new MyTask("6"));
  49. showState(queue, threadPool);
  50. }
  51. private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) {
  52. try {
  53. Thread.sleep(300);
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. List<Object> tasks = new ArrayList<>();
  58. for (Runnable runnable : queue) {
  59. try {
  60. Field callable = FutureTask.class.getDeclaredField("callable");
  61. callable.setAccessible(true);
  62. Object adapter = callable.get(runnable);
  63. Class<?> clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter");
  64. Field task = clazz.getDeclaredField("task");
  65. task.setAccessible(true);
  66. Object o = task.get(adapter);
  67. tasks.add(o);
  68. } catch (Exception e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. LoggerUtils.main.debug("pool size: {}, queue: {}", threadPool.getPoolSize(), tasks);
  73. }
  74. }

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

难易程度:☆☆☆
出现频率:☆☆☆

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

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

1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。

2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

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

ArrayBlockingQueue的LinkedBlockingQueue区别

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

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

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

3、如何确定核心线程数

难易程度:☆☆☆☆
出现频率:☆☆☆

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

  • IO密集型任务

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

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

  • CPU密集型任务

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

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

java代码查看CPU核数

参考回答:

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

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

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

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

4、线程池的种类有哪些

难易程度:☆☆☆
出现频率:☆☆☆

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

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

  • 核心线程数与最大线程数一样,没有救急线程
  • 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE
  • 适用场景:适用于任务量已知,相对耗时的任务
  • 案例:
  1. java public class FixedThreadPoolCase {
  2. static class FixedThreadDemo implements Runnable{
  3. @Override
  4. public void run() {
  5. String name = Thread.currentThread().getName();
  6. for (int i = 0; i < 2; i++) {
  7. System.out.println(name + ":" + i);
  8. }
  9. }
  10. }
  11. public static void main(String[] args) throws InterruptedException {
  12. //创建一个固定大小的线程池,核心线程数和最大线程数都是3
  13. ExecutorService executorService = Executors.newFixedThreadPool(3);
  14. for (int i = 0; i < 5; i++) {
  15. executorService.submit(new FixedThreadDemo());
  16. Thread.sleep(10);
  17. }
  18. executorService.shutdown();
  19. }
  20. }
  • 单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

  • 核心线程数和最大线程数都是1
  • 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE
  • 适用场景:适用于按照顺序执行的任务
  • 案例:
  1. java public class NewSingleThreadCase {
  2. static int count = 0;
  3. static class Demo implements Runnable {
  4. @Override
  5. public void run() {
  6. count++;
  7. System.out.println(Thread.currentThread().getName() + ":" + count);
  8. }
  9. }
  10. public static void main(String[] args) throws InterruptedException {
  11. //单个线程池,核心线程数和最大线程数都是1
  12. ExecutorService exec = Executors.newSingleThreadExecutor();
  13. for (int i = 0; i < 10; i++) {
  14. exec.execute(new Demo());
  15. Thread.sleep(5);
  16. }
  17. exec.shutdown();
  18. }
  19. }
  • 可缓存线程池

  • 核心线程数为0
  • 最大线程数是Integer.MAX_VALUE
  • 阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。
  • 适用场景:适合任务数比较密集,但每个任务执行时间较短的情况
  • 案例:
  1. java public class CachedThreadPoolCase {
  2. static class Demo implements Runnable {
  3. @Override
  4. public void run() {
  5. String name = Thread.currentThread().getName();
  6. try {
  7. //修改睡眠时间,模拟线程执行需要花费的时间
  8. Thread.sleep(100);
  9. System.out.println(name + "执行完了");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. public static void main(String[] args) throws InterruptedException {
  16. //创建一个缓存的线程,没有核心线程数,最大线程数为Integer.MAX_VALUE
  17. ExecutorService exec = Executors.newCachedThreadPool();
  18. for (int i = 0; i < 10; i++) {
  19. exec.execute(new Demo());
  20. Thread.sleep(1);
  21. }
  22. exec.shutdown();
  23. }
  24. }
  • 提供了“延迟”和“周期执行”功能的ThreadPoolExecutor。

  • 适用场景:有定时和延迟执行的任务
  • 案例:
  1. java public class ScheduledThreadPoolCase {
  2. static class Task implements Runnable {
  3. @Override
  4. public void run() {
  5. try {
  6. String name = Thread.currentThread().getName();
  7. System.out.println(name + ", 开始:" + new Date());
  8. Thread.sleep(1000);
  9. System.out.println(name + ", 结束:" + new Date());
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. public static void main(String[] args) throws InterruptedException {
  16. //按照周期执行的线程池,核心线程数为2,最大线程数为Integer.MAX_VALUE
  17. ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
  18. System.out.println("程序开始:" + new Date());
  19. /**
  20. * schedule 提交任务到线程池中
  21. * 第一个参数:提交的任务
  22. * 第二个参数:任务执行的延迟时间
  23. * 第三个参数:时间单位
  24. */
  25. scheduledThreadPool.schedule(new Task(), 0, TimeUnit.SECONDS);
  26. scheduledThreadPool.schedule(new Task(), 1, TimeUnit.SECONDS);
  27. scheduledThreadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
  28. Thread.sleep(5000);
  29. // 关闭线程池
  30. scheduledThreadPool.shutdown();
  31. }
  32. }

5、为什么不建议用Executors创建线程池

难易程度:☆☆☆
出现频率:☆☆☆

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/111560
推荐阅读
相关标签
  

闽ICP备14008679号