Java线程池 - (一)自定义线程池

Java线程池 - (一)自定义线程池

什么是线程池?

线程池就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务

为什么要使用线程池?

可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行压力

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

应用场景

只要有并发的地方、任务数量大或者小、每个任务执行时间长或者短,都可以使用线程池,只不过在使用线程吃的时候,注意一下设置合理的线程池大小即可

自定义线程池

可以参考ThreadPoolExecutor,来看下如何自定义线程池

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

1.corePoolSize - 核心线程数量
2.maximumPoolSize - 最大线程数量
这2个参数,官方文档是这样说的:

A ThreadPoolExecutor will automatically adjust the pool size (see getPoolSize()) according to the bounds set by corePoolSize (see getCorePoolSize()) and maximumPoolSize (see getMaximumPoolSize()). When a new task is submitted in method execute(java.lang.Runnable), and fewer than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads are idle. If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full. By setting corePoolSize and maximumPoolSize the same, you create a fixed-size thread pool. By setting maximumPoolSize to an essentially unbounded value such as Integer.MAX_VALUE, you allow the pool to accommodate an arbitrary number of concurrent tasks. Most typically, core and maximum pool sizes are set only upon construction, but they may also be changed dynamically using setCorePoolSize(int) and setMaximumPoolSize(int).

3.keepAliveTime - 最大空闲时间,当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
4.unit - 时间单位
5.workQueue - 任务队列,可以将其理解为一个临时的缓冲区。当线程数量达到核心线程数量后,这个时候如果再有任务提交到线程池中,此时并不会立即创建新线程,而是会把任务添加到任务队列中去。只有当任务队列加满了之后,此时,才会按照我们设置的最大线程数创建线程,当然,前提是保证当前运行线程的总数量不超过最大线程数
6.threadFactory - 线程工厂,用于创建线程
7.RejectedExecutionHandler - 饱和处理机制

流程示意图
流程示意图
可通过如下的场景来理解:

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号工作人员继续待着(池内保持核心线程数量);

自定义线程池参数设计

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)
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;

实现步骤

1.编写任务类,实现Runnable接口;
2.编写线程类,用于执行任务,需要持有所有任务;
3:编写线程池类,包含提交任务,执行任务的能力;

注意:下面的例子,并不是严格按照上面的流程图来实现的,但其实现的方式还是很有参考意义的
1.任务类MyTask

/**
 * 任务类
 */
public class MyTask implements Runnable{
    //任务编号
    private int id;

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

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println("线程:" + name + " 即将执行任务:" + id);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + name + " 完成了任务:" + id);
    }

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

2.线程类MyWorker

/**
 * 线程类
 */
public class MyWorker extends Thread {
    //线程的名称
    private String name;

    //用于保存所有的任务
    private List<Runnable> tasks;

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

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

3.线程池类MyThreadPool

/**
 * 线程池类
 */
public class MyThreadPool {
    //任务队列,需要控制线程安全问题
    private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
    //当前线程数量
    private int num;
    //核心线程数量
    private int corePoolSize;
    //最大线程数量
    private int maxSize;
    //任务队列长度
    private int workSize;

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

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

    private void execTask(Runnable r) {
        //判断当线程池中的线程总数量,是否超出了核心数
        if (num < corePoolSize) {
            new MyWorker("核心线程:" + num, tasks).start();
            num++;
        } else if (num < maxSize) {
            new MyWorker("非核心线程:" + num, tasks).start();
            num++;
        } else {
            System.out.println("任务:" + r + " 被缓存了...");
        }
    }

}

编写一个测试类,如下:

//创建线程池类对象
MyThreadPool pool = new MyThreadPool(2, 4, 20);
//提交多个任务
for (int i = 0; i < 10; i++) {
    //创建任务对象,并提交给线程池
    MyTask my = new MyTask(i);
    pool.submit(my);
}

控制台输出结果如下:

任务:MyTask{id=4} 被缓存了...
任务:MyTask{id=5} 被缓存了...
任务:MyTask{id=6} 被缓存了...
任务:MyTask{id=7} 被缓存了...
任务:MyTask{id=8} 被缓存了...
任务:MyTask{id=9} 被缓存了...
线程:非核心线程:2 即将执行任务:2
线程:非核心线程:3 即将执行任务:3
线程:核心线程:0 即将执行任务:0
线程:核心线程:1 即将执行任务:1
线程:非核心线程:3 完成了任务:3
线程:非核心线程:3 即将执行任务:4
线程:非核心线程:2 完成了任务:2
线程:非核心线程:2 即将执行任务:5
线程:核心线程:1 完成了任务:1
线程:核心线程:0 完成了任务:0
线程:核心线程:1 即将执行任务:6
线程:核心线程:0 即将执行任务:7
线程:核心线程:1 完成了任务:6
线程:非核心线程:3 完成了任务:4
线程:非核心线程:3 即将执行任务:9
线程:非核心线程:2 完成了任务:5
线程:核心线程:0 完成了任务:7
线程:核心线程:1 即将执行任务:8
线程:非核心线程:3 完成了任务:9
线程:核心线程:1 完成了任务:8

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值