06-006【JUC】java实现多线程的四种方式之四:线程池

需要先了解并学习BlockingQueue.

1.为什么用线程池及其优势

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后再线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
主要特点:线程复用;控制最大并发数;管理线程。
优势:

  1. 降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁造成的损耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

2.线程池如何使用

2.1 架构说明

线程池的底层类:ThreadPoolExecutor
在这里插入图片描述

2.2 编码实现

Executors是Executor接口的辅助工具类
了解:

  1. Executors.newScheduledThreadPool():带时间调度的;
  2. java8新出的Executors.newWorkStealingPool(int):使用目前机器上可用的处理器作为它的并行级别;(后期可以展开学习)

重点:

  1. Executors.newFixedThreadPool(int):执行长期的任务,性能好很多 一池固定数线程
//底层源码
 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

  1. Executors.newSingleThreadExecutor():一个任务一个任务执行的场景 一池一线程
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(corePoolSize: 1, maximumPoolSize: 1,
                                    keepAliveTime: 0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  1. Executors.newCachedThreadPool():适用短期异步的小程序或者负载较轻的服务器 一池多线程
  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

编码实现:

package com.magic.juc0117;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Description 第四种使用java多线程的方式,线程池
 * @Author Chelsea
 * @Date 2020/1/29 13:34
 */

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //Array  Arrays
        //Collection  Collections
        //Executor  Executors
        //一池5个处理线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        ExecutorService threadPool = Executors.newCachedThreadPool();

        //线程池关闭比使用更重要
        // 模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 0; i < 20; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 办理业务");
                });
                //线程暂停,针对可以扩容的线程池,此时不需要扩容就可以完成业务
				//try {TimeUnit.MICROSECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}
            }

        } catch (Exception e){
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

2.3 ThreadPoolExecutor

看看上面源码ThreadPoolExecutor是线程池的底层实现

3.线程池的重要参数

底层源码可知实际有七大参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. corePoolSize:线程池中的常驻核心线程数;
  2. maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1;
  3. keepAliveTime:多余的空闲线程的存活时间,当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到只剩下coolPoolSize个线程为止;
  4. unit:keepAliveTime的单位;
  5. workQueue:任务队列,被提交但尚未被执行的任务;
  6. threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可;
  7. handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝

4.线程池底层工作原理

在这里插入图片描述
线程池的主要处理流程
在这里插入图片描述
总结重点:

  1. 在创建了线程池后,等待提交过来的任务请求
  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
    1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
    2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
    3. 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
    4. 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行;
  4. 当一个线程无事可做超过一定时间(keepAliveTime)时,线程池会判断:如果当前运行的线程数大于corePoolSize,那么这个线程被停掉,所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小

5.拒绝策略

5.1 谈谈线程池的拒绝策略

  1. 是什么?
    等待队列已经排满,再也塞不下新任务了,同时,线程池中的max线程池也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理处理这个问题。
  2. jdk内置的四种拒绝策略
    1. AbortPolicy(默认):直接抛出RejectedExecutionExcep异常阻止系统正常运行。
    2. CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
    3. DiscardOldestPolicy:抛弃队列中等待队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
    4. DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

5.2 你在工作中单一的/固定数的/可变的三种创建线程池的方法,你用哪个多?(大坑)

答案是一个都不用嘿嘿~
那么既然jdk已经提供了,为什么不用呢?
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的通信更加明确线程池的运行规则,规避资源耗尽的风险。
Executors返回的线程池对象的弊端如下:

  1. FixedThreadPool和SingleThreadPool:允许请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
  2. CachedThreadPool和ScheduledThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

5.3 工作中如何使用线程池,是否自定义过线程池使用

package com.magic.juc0117;

import java.util.concurrent.*;

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,5, 1L,TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            for (int i = 0; i < 12; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务ls");
                });
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

5.4 怎样合理配置线程池

  1. CPU密集型
    CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
    CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)。
    CPU密集型任务配置尽可能少的线程数量:
    一般公式:CPU核数+1个线程的线程池(比如8核CPU是9)
	//查看CPU核数
	System.out.println(Runtime.getRuntime().availableProcessors());
  1. IO密集型
    1. 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2;
    2. IO密集型时,大部分线程都阻塞,故需要多配置线程数:参考公式:CPU核数/(1-阻塞系数) 阻塞系数在0.8-0.9直接 比如8核CPU 8/(1-0.9)=80

学习整理于面试题.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值