随笔篇-线程池

2021-11-09  本文已影响0人  秃头猿猿

线程池

1. 简介

当一个程序中需要多个task需要被并发执行时,最直接的方式是为每一个task创建一个线程去执行,但这样会带来以下问题:

在这种情况下,引入"池化技术"是必要的,在实际开发中这种技术得到很多的应用,例如数据库连接池等

池化技术可以简单理解为是一个池子,在这个池子存放着固定的资源,这些资源可以是线程,也可以数据库连接,具体取决于是怎么类型的池

这些资源不会"消失",而是可以被多次复用,从而达到节省资源开销等目的

image-20211102172615693

线程池就是"池化技术"的一种体现,使用该技术可以避免上文中提到的问题,并且好处众多,如下:

2. 参数

创建线程池会需要传入几个参数才能创建成功,如下:

参数名 说明
corePoolSize 核心线程数
maxPoolSize 最大线程数
keepAliveTime 保持存活时间
workQueue 任务存储队列
threadFactory 线程工厂
rejectHandler 拒绝处理器

关于线程池中线程创建过程大体流程如下(含参数解释):

具体流程图如下:

image-20211102180822666

ThreadFactory

线程工厂,用来创建线程的,在创建线程池时,可以使用开发人员定义的线程工厂,也可以使用默认提供的ThreadFactory

默认的ThreadFactory使用Executors.defaultThreadFactory()创建,该线程工程创建出来的线程都是在同一个线程组,且都不是守护线程

如果开发人员想要自己指定创建出来的线程名,线程组,优先级,是否为守护线程则可以使用自己的线程工厂


WorkQueue

工作队列,或者叫任务队列,用来存储任务,当核心线程已满,且核心线程都忙碌时,则将任务存储到工作队列中,直接工作队列也存储满,才会去创建非核心线程,如上文图

工作队列为阻塞队列,常用一般分为三个,可以由开发人员自由选择

3. 创建

线程池创建分为两大类:

3.1 自动

jdk默认封装的线程池主要有以下:

接下来就从其说明,使用,缺点问题方面探讨这些线程池之间的优缺点


3.1.1 FixedThreadPool

3.1.2 SingleThreadPool

该线程池从名字可以看出是单个线程,因此在创建线程池的时候也不需要传入核心线程数,如下:

 ExecutorService executor = Executors.newSingleThreadExecutor();

从其创建的源码可知,该线程池使用的也是"无边界阻塞队列",如下:

image-20211109094219578

因此如果在当个任务处理的情况下,也会发生OOM,原理同FixThreadPool原理一样,这里就不再演示

3.1.3 CachedThreadPool

3.1.4 ScheduledThreadPool

该类型的线程池,是一个具备周期性执行的一个线程池,在这里不再解释,之前的章节有过详细描述

3.2 手动

4. 核心数

当手动创建线程池时,如何去确定核心线程数为多少,目前在一些实践中主要分为以下两类:

5. Factory

在上述中,不管是手动创建线程池还是自动创建线程池,使用的ThreadFactory都是默认的线程工厂

有时候在创建线程池时,想要修改线程池一些参数,例如线程名字等,这样就可以使用自定义线程工厂,如下:

package com.tomato.thread.pool;

import java.text.MessageFormat;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import static java.text.MessageFormat.*;

public class CustomThreadFactory implements ThreadFactory {
    private ThreadGroup threadGroup;

    private AtomicLong threadNumber = new AtomicLong(1L);

    /**
     * 线程池中线程组名字前缀
     */
    private String prefix;

    public CustomThreadFactory() {
        this("");
    }

    public CustomThreadFactory(String prefixName) {
        SecurityManager securityManager = System.getSecurityManager();
        threadGroup = securityManager != null ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup();
        prefix = (prefixName != null && !"".equals(prefixName)) ? prefixName : "tomato-pool-thread";
    }

    @Override
    public Thread newThread(Runnable r) {
        /**
         * 当设置stackSize属于<=0 时,以-Xss为准
         * 当设置stackSize属于(0, 4k]区间时,设置的-Xss会失效,栈空间取默认值1M
         * 当设置stackSize属于(4k, 64k]区间时,设置的-Xss会失效,栈空间取4k。
         * 当设置stackSize属于(64k, 128k]区间时,设置的-Xss会失效,栈空间取64k。
         * 当设置stackSize属于 >128k 时,设置的-Xss会失效,栈空间取stackSize本身
         */
        Thread thread = new Thread(threadGroup,r, prefix + "-" + threadNumber.getAndIncrement(), 0);

        if (thread.isDaemon()) {
            thread.setDaemon(false);
        }

        if (thread.getPriority() != Thread.NORM_PRIORITY) {
            thread.setPriority(Thread.NORM_PRIORITY);
        }

        return thread;
    }
}

class Test {
    public static final Integer ACPU = Runtime.getRuntime().availableProcessors();
    public static void main(String[] args) {
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(
                        ACPU * 2 + 1,
                        ACPU * 2 + 1,
                        0,
                        TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(600),
                        new CustomThreadFactory());

        for(int i = 1; i < 1_00; i++) {
            executor.execute(() -> {
                System.out.println(format("{0}执行任务完成",Thread.currentThread().getName()));
            });
        }
    }
}

执行结果,发现线程的名字都改变,如下:

image-20211109134421034

这样就完成了自定义ThreadFactory的实现

6. Reject

从上文的描述中可知,线程池的流程是当任务存储队列满了的时候,则会创建非核心线程去执行队列中的任务

那如果所有的线程都处于忙碌中,且队列中存储满了,那么当任务再过来就会执行配置的

拒绝策略去拒绝任务,在线程池中拒绝策略主要有三个,如下:

如果线程池异常关闭,有任务过来也会异常拒绝

关于四种策略,其详细解释如下文

6.1 AbortPolicy

6.2 CallerRunsPolicy

6.3 DiscardOldestPolicy

6.4 DiscardPolicy

6.5 sumary

四种策略,具体采用哪种策略还是需要根据具体的业务场景来实现

代码地址: https://gitee.com/wangzh991122/thread-juc

上一篇 下一篇

猜你喜欢

热点阅读