01_线程池概念和自定义线程池

1,什么是线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;

2,为什么使用线程池

使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处;

3,使用线程池有哪些优势

  • 1:线程和任务分离,提升线程重用性;
  • 2:控制线程并发数量,降低服务器压力,统一管理所有线程;
  • 3:提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;

4,线程池应用场景介绍

  • 应用场景介绍
1:网购商品秒杀
2:云盘文件上传和下载
3:12306网上购票系统等
  • 总之
    只要有并发的地方、任务数量大或小、每个任务执行时间长或短的都可以使用线程池;
    只不过在使用线程池的时候,注意一下设置合理的线程池大小即可;(关于如何合理设置线程池大小在后面的章节中讲解)

5,java内置线程池

我们要想自定义线程池,必须先了解线程池的工作原理,才能自己定义线程池;
这里我们通过观察java中ThreadPoolExecutor的源码来学习线程池的原理;
(源码演示在idea中查看)

6,ThreadPoolExecutor部分源码

构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
                              int maximumPoolSize,//     最大线程数
                              long keepAliveTime, //       最大空闲时间
                              TimeUnit unit,         //        时间单位
                              BlockingQueue<Runnable> workQueue,   //   任务队列
                              ThreadFactory threadFactory,    // 线程工厂
                              RejectedExecutionHandler handler  //  饱和处理机制
	) 
{ ... }

6.1 ThreadPoolExecutor参数详解

我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数;
a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0),
于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);
在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2);
紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候,
并告知他: 如果1、2号工作人员空出,c就可以前去办理业务;
此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务;
假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数),
于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户;
最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这部分空闲的员工人下班.(销毁线程)
但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量);

6.2 线程池工作流程总结示意图

在这里插入图片描述

6.3,自定义线程池-参数设计分析

通过观察Java中的内置线程池参数讲解和线程池工作流程总结,我们不难发现,要设计一个好的线程池,就必须合理的设置线程池的4个参数;那到底该如何合理的设计4个参数的值呢?

  • 1:核心线程数(corePoolSize)
    核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;
  • 2:任务队列长度(workQueue)
    任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
  • 3:最大线程数(maximumPoolSize)
    最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;
  • 4:最大空闲时间(keepAliveTime)
    这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;

7,自定义线程池

7.1 实现步骤

在这里插入图片描述

7.2 创建 任务类

package com.study.customThreadPool.demo01;

import lombok.extern.slf4j.Slf4j;

/**
 * 需求:
 *  自定义线程池练习,这是任务类,需要实现Runnable接口
 *  包含任务编号,每一个任务执行时间设计为0.2秒
 */
//任务类
@Slf4j
public class MyTask implements Runnable{

    private int id;

    public MyTask(int id) {
        this.id = id;
    }


    @Override
    public void run() {
        //获取线程名称
        String name = Thread.currentThread().getName();
        log.info("线程:"+name+"即将执行的任务是"+id);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("线程:"+name+",完成任务是"+id);

    }

    @Override
    public String toString() {
        return "MyTask{" +
                "id=" + id +
                '}';
    }
}

7.3 创建 线程类

package com.study.customThreadPool.demo01;

import java.util.List;

/**
 * 1,编写线程类,需要集成Thread,设计一个属性,
 *      用于保存线程名称
 * 2,设计一个集合用于保存所以的任务
 */
public class MyWorker extends Thread {
    private String name; //保存线程名称

    private List<Runnable> task;

    public MyWorker(String name,List<Runnable> task) {
        super(name);
        this.task = task;
    }

    @Override
    public void run() {
        //判断集合中是否有任务,只要有就一直执行
        while (task.size()>0){
            Runnable r = task.remove(0);
            r.run();
        }
    }
}

7.4 创建 线程池类

package com.study.customThreadPool.demo01;

import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * 线程池类
 *  成员变量
 *      1:任务队列  集合   (需要控制线程安全问题)
 *      2:当前线程数量
 *      3:核心线程数量
 *      4:最大线程数量
 *      5:任务队列长度
 *  成员方法:
 *      1:提交任务:
 *          将任务添加到集合,需要判断是否超出了任务总长度
 *      2:执行任务
 *          判断当前线程的数量,决定创建核心线程还是非核心线程
 */
@Slf4j
public class MyThreadPool {
    //1:任务队列
    private List<Runnable> tasks =
            Collections.synchronizedList(new LinkedList<>());

    //2:当前线程数量
    private int num;

    //3:核心线程数量
    private int corePoolSize;

    //4:最大线程数量
    private int maxPoolSize;

    //5:任务队列长度
    private int workSize;

    public MyThreadPool(int corePoolSize, int maxPoolSize, int workSize) {
        this.corePoolSize = corePoolSize;
        this.maxPoolSize = maxPoolSize;
        this.workSize = workSize;
    }

    //提交任务
    public void submit(Runnable r){
        //判断集合中的数量是否超出最大任务数量
        if(tasks.size()>=workSize){
            log.info("任务:"+r+"被丢弃了...");
        }else{
            tasks.add(r);
            //执行任务
            execTask(r);
        }
    }

    /**
     * 执行任务方法
     * @param r
     */
    private void execTask(Runnable r) {
        //判断当前线程池中线程总数量是否超出核心数

        if(num < corePoolSize){ //判断当前线程数是否小于corePoolSize
            new MyWorker("核心线程:"+num,tasks).start();
            num ++;
        }else if(num < maxPoolSize){
            new MyWorker("非核心线程:"+num,tasks).start();
            num ++;
        }else{
            log.info("任务:"+r+"已经被缓存。。。");
        }
    }
}

7.5 创建 测试类

package com.study.customThreadPool.demo01;

public class MyTest {
    public static void main(String[] args) {
        //创建线程池类对象
        MyThreadPool pool = new MyThreadPool(2, 4, 5);

        //提交多个任务
        for (int i = 0; i < 30; i++) {
            MyTask task = new MyTask(i);
            pool.submit(task);
        }
    }
}

7.6 测试

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值