Java多线程学习笔记(七)--线程池

淩亂°似流年 2022-05-24 03:35 437阅读 0赞

强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan

什么是线程池

为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用,线程池是来管理线程的,线程池中总有那么几个活跃线程,当使用线程时可以从池子中随便拿一个空闲线程,当完成工作时,不用立即关闭线程,而是将线程还给线程池,方便下一个要使用线程的任务,有了线程池后,创建线程变成是从线程池中拿线程,销毁线程时变成是将线程还给线程池继续管理。 这种场景非常像在开发中连接数据库,有一个数据库连接池,需要创建连接时是从连接池中拿一个连接,释放时是将连接还给连接池

解决了什么问题

**1.多线程解决了什么问题:**之前的电脑是单核的,cpu的资源在多个线程间来回切换,现在发展的很快,电脑大多都是多核的,多线程可以考虑并行的问题,可以有效的利用cpu的资源,提高资源利用率和系统运行效率 **2.线程池解决了什么问题:**创建和销毁线程时需要消耗资源,创建出来的线程是占用内存的,线程池是来管理线程的,那么问题来了,什么时候使用线程池呢?是需要使用多线程解决问题的时候就使用线程池吗?答案是否定的,因为刚才说过了,创建出来的线程是占用空间的,因此线程池的使用场景是在需要大量线程的情况下,我们使用线程池管理线程,相当于把线程池的创建和销毁均分给了多个任务,在未使用线程池的时候是每个任务执行时都需要去创建和销毁线程,因此,线程池在资源耗费较大的时候,可以有效的避免频繁的创建和销毁线程,从而节省资源,线程本身也会占用内存空间,如果处理不当可能会导致Out of Memory异常,因此选择正确的等待队列是必要的,具体的会在下边详细讲解。

Executor框架结构图

![本图来自网上](https://img-blog.csdn.net/20180519153225545?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poMTU3MzI2MjE2Nzk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 上图来自于网络 1.Executor是该框架结构图中的最基础的接口,其中定义了线程池execute方法 2.ExecutorService也是一个接口,继承自Executor,定义了线程池的提交及停止,及其他一些判断线程池状态的方法 3.ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务 4.ScheduledExecutorService提供定时执行功能 5.还有一个是Executors,在本图中没有写出,Executors充当一个工厂角色,通过它可以取得一个拥有特定功能的线程池,目前提供的有四种线程池

核心线程池的内部实现

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }
  7. public static ExecutorService newCachedThreadPool() {
  8. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  9. 60L, TimeUnit.SECONDS,
  10. new SynchronousQueue<Runnable>());
  11. }

以上三种线程池的实现都是对ThreadPoolExecutor进行了封装,ThreadPoolExecutor这个类为什么如此的功能强大?它的最重要的构造函数如下:

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory,
  7. RejectedExecutionHandler handler)

各参数说明:
1.corePoolSize:核心线程数量,指定了线程池中的线程数量,当使用Executors创建了一种线程池时,线程池中是没有活跃的线程的,当提交一个任务时,如果当前线程池中活跃线程数量小于核心线程数量,则会创建一个线程,这个线程就是活跃线程,当线程池中的线程数量达到核心线程数量,再来一个任务需要线程时,这个任务会被放到队列中,等待线程空闲
2.maximumPoolSize:指定了线程池中最大允许的线程数量,当任务来了,队列中已经满了,此时如果线程池中的线程数量没有超过maximumPoolSize的值,则继续创建线程执行任务
3.keepAliveTime:当线程池中的线程数量超过corePoolSize时,其他被创建出来的线程(不是核心线程的线程)的空闲时的存活时间
4.unit:keepAliveTime的单位,包括:

  1. TimeUnit.DAYS; //天
  2. TimeUnit.HOURS; //小时
  3. TimeUnit.MINUTES; //分钟
  4. TimeUnit.SECONDS; //秒
  5. TimeUnit.MILLISECONDS; //毫秒
  6. TimeUnit.MICROSECONDS; //微妙
  7. TimeUnit.NANOSECONDS; //纳秒

5.workQueue:任务队列,被提交单尚未被执行的任务,队列类型如下:

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。该类型的构造函数必须带一个容量参数,表示该队列的最大容量。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。提交的任务不会被真是的保存,而总是将新任务提交给线程执行。
  • LinkedBlockingQueue,无解队列,使用时,除非系统资源耗尽,否则,任务是不存在入队失败的情况,
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。总能确保优先级高的任务优先执行

    无界队列和有界队列各有优缺点,无界队列在任务无限多时,无界队列膨胀,耗尽资源,有界队列,可能会造成部分任务被拒绝
    6.threadFactory:线程工厂,用于创建线程
    7.handler:拒绝策略,当任务太多,队列满了,线程池中的线程数量达到了最大的线程数量后,就需要执行拒绝策略,JDK提供了四种拒绝策略,如下:

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作
  • CallerRunsPolicy策略:直接在execute方法的调用线程中运行被拒绝的任务,如果执行程序已经关闭,则会丢弃该任务
  • DiscardOledestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被调度执行的任务,并尝试再次提交当前任务
  • DiscardPolicy策略:直接丢弃任务,不予任何处理

任务执行过程

由以上的ThreadPoolExecutor的参数详解了解了各参数都是干什么的,这几个参数是如何协同工作,完成任务执行呢? **源码解析:**

  1. public void execute(Runnable command) {
  2. // 命令为null,抛出异常
  3. if (command == null)
  4. throw new NullPointerException();
  5. /*
  6. * 进行下面三步
  7. *
  8. * 1. 如果运行的线程小于corePoolSize,则尝试使用用户定义的Runnalbe对象创建一个新的线程
  9. * 调用addWorker函数会原子性的检查runState和workCount,通过返回false来防止在不应
  10. * 该添加线程时添加了线程
  11. * 2. 如果一个任务能够成功入队列,在添加一个线城时仍需要进行双重检查(因为在前一次检查后
  12. * 该线程死亡了),或者当进入到此方法时,线程池已经shutdown了,所以需要再次检查状态,
  13. * 若有必要,当停止时还需要回滚入队列操作,或者当线程池没有线程时需要创建一个新线程
  14. * 3. 如果无法入队列,那么需要增加一个新线程,如果此操作失败,那么就意味着线程池已经shut
  15. * down或者已经饱和了,所以拒绝任务
  16. */
  17. int c = ctl.get();// 获取线程池控制状态
  18. if (workerCountOf(c) < corePoolSize) {// worker数量小于corePoolSize
  19. if (addWorker(command, true)) // 添加worker
  20. return;// 成功则返回
  21. c = ctl.get();// 不成功则再次获取线程池控制状态
  22. }
  23. if (isRunning(c) && workQueue.offer(command)) {// 线程池处于RUNNING状态,将命令(用户自定义的Runnable对象)添加进workQueue队列
  24. // 再次检查,获取线程池控制状态
  25. int recheck = ctl.get();
  26. if (! isRunning(recheck) && remove(command)) // 线程池不处于RUNNING状态,将命令从workQueue队列中移除
  27. reject(command); // 拒绝执行命令
  28. else if (workerCountOf(recheck) == 0) // worker数量等于0
  29. addWorker(null, false);// 添加worker
  30. }
  31. else if (!addWorker(command, false))// 添加worker失败
  32. reject(command);// 拒绝执行命令
  33. }

这里写图片描述

4种线程池

**1.newFixedThreadPool()固定大小的线程池** ``` public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } ```

该方法返回一个固定大小的线程池,看这种类型的线程池强两个参数corePoolSize和maximumPoolSize都是nThreads,因此它是固定大小的线程池,刚创建时,线程池中是没有活跃线程的,当创建的线程数量达到corePoolSize后,线程池中的线程数量是一直不变的
2.newSingleThreadExecutor()

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }

该方法返回一个只有一个线程的线程池,若多余一个任务被提交到该线程池,任务会被保存在一个队列中,待线程空闲,按先进后出的顺序执行队列中的任务
3.newCachedThreadPool()

  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  5. }

该方法返回一个可根据实际情况调整线程数量的线程池,线程池的线程数量不确定,若空闲线程可复用,则会优先使用可复用的线程
4.newSingleThreadScheduledExecutor()

  1. public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
  2. return new DelegatedScheduledExecutorService
  3. (new ScheduledThreadPoolExecutor(1));
  4. }

该方法返回一个ScheduledExecutorService对象,线程池大小为1

推荐博客

  • Excutor框架:https://www.cnblogs.com/study-everyday/archive/2017/04/20/6737428.html
  • 四种拒绝策略解析:https://blog.csdn.net/pozmckaoddb/article/details/51478017
  • 多线程线程池:https://blog.csdn.net/why15732625998/article/details/80046147

发表评论

表情:
评论列表 (有 0 条评论,437人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线()JDK原生线

    如同数据库连接一样,线程的创建、切换和销毁同样会耗费大量的系统资源。为了复用创建好的线程,减少频繁创建线程的次数,提高线程利用率可以引用线程池技术。使用线程池的优势有如下几点: