12

110 天前

@Vedar @1194129822 @lancelee01 @micean 感谢各位的热情解答,我很受启发。再结合朋友给的例子,我仔细读了下源码,已经大致能复盘这个错误了。

**inner 线程池 reject 的原因:**

1. 主要原因:队列太小,这里给的是 1,实际每个 outer 线程要产生 3 个任务

2. 次要原因:outter 线程里面使用 countdownlatch 确实不能起到很好的限流作用,

**次要原因分析:**

如 runWorker()源码所示,run 执行完毕并不能代表线程任务执行完毕。这意味着 outter 线程与 inner 线程的空闲线程数可能不是 1:3 的关系。但这里可以通过让 outter 线程 sleep 等待 inner 先执行完成,规避这个因素的影响。规避后,问题还是会存在,说明不是主要原因。

**主要原因分析:**

先来看个案例

```

static class MyLinkedBlockingQueue extends LinkedBlockingQueue {

public MyLinkedBlockingQueue(int capacity) {

super(capacity);

}

@Override

public boolean offer(E o) {

System.out.println("任务加入,当前队列数:" + this.size());

return super.offer(o);

}

}

public static void main(String[] args) throws InterruptedException {

BlockingQueue queue = new MyLinkedBlockingQueue<>(1);

// 3 个线程的线程池

ThreadPoolExecutor taskPoolExecutor = new ThreadPoolExecutor(3, 3, 30, TimeUnit.SECONDS, queue);

// 先将线程池拉满

for (int i = 0; i < 3; i++) {

final int finalI = i;

taskPoolExecutor.execute(() -> {

logger.info("{}", finalI);

});

}

// 等待全部任务执行完

Thread.sleep(1000);

// 再次执行任务,发现每一个任务都触发加入队列操作。

for (int i = 10; i < 12; i++) {

final int finalI = i;

// 多线程更容易触发 reject

// new Thread(()-> taskPoolExecutor.execute(() -> logger.info("{}", finalI))).start();

taskPoolExecutor.execute(() -> logger.info("{}", finalI));

}

}

```

执行结果:

> 23:12:39.988 [pool-1-thread-3] INFO c.r.s.Demo8.lambda$main$0:34 - 2

23:12:39.988 [pool-1-thread-2] INFO c.r.s.Demo8.lambda$main$0:34 - 1

23:12:39.988 [pool-1-thread-1] INFO c.r.s.Demo8.lambda$main$0:34 - 0

任务加入,当前队列数:0

23:12:40.997 [pool-1-thread-3] INFO c.r.s.Demo8.lambda$null$1:46 - 10

任务加入,当前队列数:0

23:12:41.000 [pool-1-thread-2] INFO c.r.s.Demo8.lambda$null$1:46 - 11

跑完这个案例我感觉我根本不懂线程池,我翻了下源码:

```

public void execute(Runnable command) {

...

int c = ctl.get();

if (workerCountOf(c) < corePoolSize) {

if (addWorker(command, true))

return;

c = ctl.get();

}

// 线程池满了后,直接不创建核心线程了

// 这里 isRunning 看的我懵逼,明明任务都执行完了,为啥还是 isRunning,先接受,后面再研究 [1]

// 然后就触发入队列

if (isRunning(c) && workQueue.offer(command)) {

int recheck = ctl.get();

if (! isRunning(recheck) && remove(command))

reject(command);

else if (workerCountOf(recheck) == 0)

addWorker(null, false);

}

else if (!addWorker(command, false))

reject(command);

```

我以为的线程池是:只要有空闲线程,任务是直接丢给线程去执行的。

**实际情况是:当核心线程数满,不管已有线程是否空闲,任务是先丢到队列,然后空闲线程从队列里面自取。**

案例中,我给的队列大小是 1,当队列满的时候,会扩容线程池到最大线程池大小到 12,此时如果队列是满的(不管线程是否空闲),继续添加就会 reject 。案例中每组有三个任务,只要线程从队列 take 任务不及时,队列很容易满,从而触发 reject 。

**验证:**

1. countDownLatch.await(); 后面加上 sleep,让 outter 线程等 inner 线程结束,排除最开始说的第二个因素的影响。

2. 将队列改成 3,适当调整线程执行时间(也可以不调),reject 很少触发或不触发。

3. 将队列改成 9,没有触发 reject

**总结:**

1. 这个任务表面是多线程嵌套调用,内外线程调度不确定性导致的线程池问题,其实本质是对线程池理解不对导致线程池滥用的问题。

2. 任务是添加到队列,空闲线程调用 take()获取,而不是有空闲线程就直接丢到空闲线程(实际任务也难以主动去找空闲线程,还容易造成等待,让线程自取则是生产消费的模式。)

3. isRunning(c) 这个方法以及相关机制,还要再研究一下。

再次感谢各位,如有不对的地方,还请指出。。

java 镶嵌创建线程_请教一个 Java 多线程嵌套使用的问题相关推荐

  1. java 64内存不足_请教一个 Java 内存占用的问题

    第 1 条附言  ·  364 天前 2020-03-04 01:08:55.525 [HikariPool-1 housekeeper] WARN c.z.hikari.pool.HikariPoo ...

  2. java 线程中创建线程_如何在Java 8中创建线程安全的ConcurrentHashSet?

    java 线程中创建线程 在JDK 8之前,还没有办法在Java中创建大型的线程安全的ConcurrentHashSet. java.util.concurrent包甚至没有一个名为Concurren ...

  3. java 镶嵌创建线程_Java多线程——之一创建线程的四种方法

    1.实现Runnable接口,重载run(),无返回值 package thread; public class ThreadRunnable implements Runnable { public ...

  4. java编写salary函数_编写一个Java程序,在程序中包含一个Employee类,Employee类包含name、age、salary三个成员变量...

    编写一个Java程序,在程序中包含一个Employee类,Employee类包含name.age.salary三个成员变量,Employee类中有4个构造方法,分别为无参的.带一个参数用来对name属 ...

  5. java输入字符串异常_设计一个 Java 程序,自定义异常类,从命令行(键盘)输入一个字符串,如果该字符串值为“XYZ”。。。...

    设计一个 Java 程序,自定义异常类,从命令行(键盘)输入一个字符串,如果该字符串值为"XYZ",则抛出一个异常信息"This is a XYZ",如果从命令 ...

  6. java hdfs创建文件_使用HDFS java api 创建文件出错。

    //创建文件核心代码 public static void createNewHDFSFile(String toCreateFilePath, String content) throws IOEx ...

  7. java结果分行显示_编写一个java程序。分行显示自己的姓名,地址,电话!用Test.java命名。_学小易找答案...

    [单选题]16.骨骼肌进行完全强直收缩时,相邻两次刺激的时间间隔应 [填空题]实习岗位名称 [单选题]神经调节的基本方式是: [单选题]Thank you for your nice gifts. - ...

  8. Java web 服务器 搭建_搭建一个java web服务端

    最近也是做了一个简单的java web 项目,由于以前也是没接触过,在这里记录下搭建一个web服务端的过程. 一般我们做一个服务端要么在本地自己的电脑上先安装环境,一般是windows系统,主要安装j ...

  9. java单链表例子_写一个java链表的例子?随便举例说一下。

    展开全部 //单链表类 package dataStructure.linearList; import dataStructure.linearList.Node; //导入单链表结点类 impor ...

  10. 用java编写打印时间_编写一个java程序,读取系统时间,然后将时间用中文输出...

    展开全部 import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.Calendar; ...

最新文章

  1. php mysql 防 sql注入_php 防sql注入方法
  2. CMenu类的使用方法
  3. vistualSVN server:Windows下SVN服务器利器
  4. 干货|各种WAF绕过手法学习
  5. win10 ie中没有java,win10没有ie浏览器怎么处理_window10找不到ie浏览器如何解决
  6. 机器学习笔记十一之降维
  7. Java操作数据库详解
  8. SQLi LABS Less-31
  9. C语言const:禁止修改变量的值
  10. 流畅的Python---list排序和保持有序序列
  11. 内核调试工具 — kdump crash
  12. breadweb控制台下载_路由器刷breed web控制台助手
  13. SpringBoot整合腾讯云直播,生成推拉流配置及工具类详细讲解!
  14. BIO、NIO、AIO网络编程
  15. 强连通分量SCC(Tarjan)
  16. 多元线性回归的缺陷_多元线性回归常见问题
  17. 4相5线步进电机驱动原理
  18. 区块链开发指南_区块链开发权威指南
  19. CGAL 凹包(alpha-Shape)
  20. antd Upload组件上传状态一直处于uploading

热门文章

  1. ECCV2020 | CPNDet:Anchor-free两阶段的目标检测框架,详解
  2. 在函数中如何获取 线程对象、线程唯一ID
  3. 设计模式-适配器模式(Adapter)
  4. H - 数论中的异或 HRBUST - 1688
  5. 使用localhost调试本地代码,setcookie无效
  6. 使用jquery获取父元素或父节点的方法
  7. 考场自动安排工具开发手记
  8. python-绘制双轴柱状图
  9. 拉格朗日插值多项式及其余项
  10. java调用scala内部类_scala中的内部类 == 简单示例