1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

多线程之线程池(中)

前言

昨天我们分享了线程池的相关知识点,我们先做一个简单回顾,昨天的内容主要是围绕线程池的构造方法,解释了各个参数的作用,以及如何定义一个线程池,最后我们通过一段示例代码,展示了各个参数的作用,同时也演示了不同参数下线程池运行状态情况,最终我们得出的结论是:

线程池能够处理的最大任务数是corePoolSize + maximumPoolSize + workQueue.size()

但是由于时间原因,昨天还有一些知识点没来得及分享,所以今天我们来继续看下剩余的内容。

线程池

昨天我们说如果不设定workQueue的大小,那永远都不会报拒绝这个错误,当然maximumPoolSize 也就无效了,今天我们就先来演示下这个问题

代码和昨天的类似:

int corePoolSize = 10;
int maximumPoolSize = 20;
long keepAliveTime = 1000;
TimeUnit unit = TimeUnit.MICROSECONDS;
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
for (int i = 0; i < 80; i++) {
    System.out.println(i + " # " + threadPoolExecutor.toString());
    threadPoolExecutor.execute(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println("hello threadPool: "+ name);
    });
}
threadPoolExecutor.shutdown();
}

只是我们现在的工作队列是没有指定大小的,其他参数和昨天是一样的,循环次数依然是80次,然后我们运行下:

这时候虽然没有线程池拒绝的错误,但是我们发现虽然最开始设定的最大线程数是20,但是在整个运行过程中,不论工作队列有多大,线程池始终只有10个线程,说明最大线程数失效了;

不过这一点也很容易理解,因为最大线程数只有在工作队列排满的时候才会生效,现在我们没有设定工作队列的大小,也就是大小无上限,这样工作队列永远不会满,所以永远都不会触发最大线程的设定。

线程存活时间

keepAliveTime这个参数昨天忘记讲了,这个参数是设定线程的存活时间,如果任务很多,并且每个任务执行时间比较短的话,调大这个参数可以提高线程的利用率。

官方文档中给出的解释是keepAliveTIme表示当线程数大于核心时,多余空闲线程在终止前等待新任务的最长时间。

需要注意的是,这个参数主要是设定工作队列中的线程存活时间,从系统源码中就可以看出这一点:

在获取任务的时候,会从工作队列中拿出并删除(poll)一个runnable线程,poll的方法实现会根据存活时间做业务处理,但是根据系统代码,我发现这个参数只有当工作队列为空的时候才会起作用,这也就是说这个参数其实就是让线程在处理完所有任务(工作队列中没有任务)之后,等等我们设定的存活时间,然后再销毁。

那我们是不是可以根据官方的解释以及keepAliveTime大胆猜测,多余线程(超过基本线程数的线程)是在工作队列为空的时候开始销毁的,因为这样这个存活时间才有意义,因为核心线程从初始化之后会一直存活

下面我们把代码简单修改下,验证下我们的推论:

int corePoolSize = 10;
int maximumPoolSize = 30;
long keepAliveTime = 20000;
TimeUnit unit = TimeUnit.MICROSECONDS;
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>(170);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
CountDownLatch countDownLatch = new CountDownLatch(200);
for (int i = 0; i < 200; i++) {
    System.out.println(i + " # " + threadPoolExecutor);
    threadPoolExecutor.execute(() -> {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println("hello threadPool: "+ name);
        countDownLatch.countDown();
    });
}
countDownLatch.await();
System.out.println("循环完成,现在的线程池状态 # " + threadPoolExecutor);
Thread.sleep(10000);
System.out.println("循环完成,休眠10秒线程池状态 # " + threadPoolExecutor);
Thread.sleep(10000);
System.out.println("循环完成,休眠20秒线程池状态 # " + threadPoolExecutor);
threadPoolExecutor.shutdown();

我们增加了几行打印,同时也把循环次数、工作队列大小和存活时间调大,增加countDownLatch主要是控制线程执行顺序,还增加了一个睡眠时间,然后运行:

虽然结果和我们预期的差不多,但还是有出入,根据运行结果,我们发现从执行完成的第10秒,线程池的大小就变成了10,也就是我们的核心线程数量,当然这结果是没有问题的,因为存活时间是从任务分发完成(也就是工作队列为空)开始算起的,分发过程肯定很快,而且那时候好多线程还没有开始运行,所以运行完成休眠10秒就达到了线程的存活时间,然后多余的线程就被销毁了。

关闭线程池

关闭线程池很简单,只需要在程序结尾执行如下代码即可:

threadPoolExecutor.shutdown();

但是这个关闭并不是立即生效,首先这个方法会检查是否有关闭权限,然后才能提交关闭操作,尽管这个样,它也要等待所有线程执行完成后才能关闭,哪怕你是直接在线程内部执行关闭操作:

运行结果:

从结果中我们可以看出来,虽然在线程内部关闭了线程池,但是线程池还是等到所有线程执行完成后才关闭,而且在线程池关闭前,线程资源的释放是逐步进行的,而不是最后才释放。

如果直接在execute()方法前执行线程池关闭命令,execute()方法在运行的时候就会报错:

需要注意的是,线程池使用完之后,必须要关闭,否则当前方法会被阻塞

就像上面这样,虽然方法执行完成了,但是主线程并未退出,线程池一直在等待新的任务进来,所以资源一直无法释放。

总结

今天我发现又讲不完了😂,不过没关系,分三次讲可以更详细一点。今天我们主要分享了三个知识点:

  • 不设定workQueue大小,maximumPoolSize 参数失效问题
  • 线程池线程存活时间的分析探讨
  • 线程关闭问题

同时也针对这三个知识点做出了简单论证,通过今天的内容,可以让我们对线程池的生命周期有更深入更全面的了解和认识,明天我们会分享线程池的拒绝策略、线程工厂等相关知识,同时也会有一些补充内容,后天应该会就线程池这块做一个小结,再后面就是多线程部分的查漏补缺和总结了,好了,今天就先到这里吧!

posted @ 2021-07-15 13:27  云中志  阅读(38)  评论(0编辑  收藏  举报