线程池 — Executor

目录

一、线程池

1. 两级调度模型

2. Executor执行过程

3. Executor结构组成

4. 线程池参数配置

二、代码示例

1. 定义线程池

2. 自定义线程工厂

3. 调用

4. 其他测试

三、源码解析

1. 类图

2. Executor与ExecutorService源码对比

3. CachedThreadPool详解

4. ScheduledThreadPoolExecutor详解

5. Future源码

6. FutureTask详解

四、参考资料


一、线程池

        线程池目的:通过减少频繁创建和销毁线程来降低性能损耗。每个线程都需要一个内存栈,用于存储局部变量、操作栈等信息,可以通过-Xss参数来调整每个线程栈大小(默认64位OS为1024KB)。

        线程池一般配合队列一起工作。设置队列的大小,当超出该阈值后,就进入拒绝策略来处理,从而保护系统免受大流量而导致崩溃。

        创建多少线程?要根据实际业务情况来压测决定。任务类型IO密集型还是CPU密集型、CPU核数,来设置合理的线程池大小、队列大小、拒绝策略等参数。maximumPoolSize(最大核心线程数)设置过大,导致瞬间线程数很多,同时使用LinkedBlockingQueue有大量任务等待线程执行,则会出现GC慢等问题,造成系统响应慢甚至OOM。

        注意线程池执行中无法捕获堆栈上下文,需要记录相关参数,以方便定位问题。

1. 两级调度模型

        Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,操作系统线程也会被回收。

        如下图所示是Executor框架的两级调度模型。Executor调度任务给线程池里的线程;操作系统调度CPU执行线程。注意:操作系统调度不受Executor调度的影响

Executor框架的两级调度模型

2. Executor执行过程

        如下图所示是ThreadPoolExecutor执行示意图。步骤如下,参考上图:

  • 1)当前运行的线程数 < corePoolSize,则创建新线程来执行任务(需要获取全局锁);
  • 2)当前运行的线程数 >= corePoolSize,则任务加入阻塞BlockingQueue;
  • 3)阻塞队列BlockingQueue已满,则创建新的线程来处理任务(需要获取全局锁);
  • 4)如果创建新线程将使当前运行的线程 > maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

        注意:尽量避免创建新线程,原因是获取全局锁,但是大部分任务加入阻塞队列中;创建的线程会封装成java.util.concurrent.ThreadPoolExecutor.Worker。 

ThreadPoolExecutor执行示意图

3. Executor结构组成

        如下图所示是Executor的使用。可以看出3大部分组成:

  • 定义任务:实现Runnable(无返回)接口或Callable接口(有返回);
  • 执行任务:线程执行器(ThreadPoolExecutor、ScheduledThreadPoolExecutor);
  • 任务结果:接口Future和实现Future接口的FutureTask类。 

4. 线程池参数配置

参数说明
corePoolSize核心线程数:即使线程空闲也不会释放(最小线程数)
maximumPoolSize最大线程数:线程池最多维护的线程数
keepAliveTime线程空闲时间:超出该时间,线程被销毁回收,直至corePoolSize
threadFactory线程工厂:创建线程,设置线程名、是否是后台线程
workQueue

任务队列:

        ArrayBlockingQueue:有界阻塞数组队列

        LinkedBlockingQueue:有界/无界阻塞链表队列

        PriorityBlockingQueue:优先级阻塞队列

        SynchronousQueue:无缓冲阻塞队列

rejectedExecutionHandler缓冲队列满后的拒绝策略,见上图

注意:a. 有界阻塞队列需要合理设置队列大小;

           b. SynchronousQueue:当插入一个任务时,必须要移除另一个任务;移除任务时,则必须插入另一个任务。(没有缓冲)

二、代码示例

1. 定义线程池

类型特点
Executors.newSingleThreadExecutor()

1. 单线程线程池

2. 线程空闲时间是0L;

3. 阻塞队列是LinkedBlockingQueue。

Executors.newScheduledThreadPool(corePoolSize)

1. 固定数量线程池

2. 线程空闲时间是0L;

3. 阻塞队列是LinkedBlockingQueue。

Executors.newCachedThreadPool()

1. 可缓存线程池(按任务需要创建线程)

2. 线程空闲时间是60L;

3. 阻塞队列是SynchronousQueue。

Executors.newScheduledThreadPool(corePoolSize)

1. 固定数量的延时或周期执行线程池

2. 线程空闲时间是0L;

3. 阻塞队列是DelayedWorkQueue。

Executors.newWorkStealingPool(parallelism)

1. Fork/Join线程池(ForkJoinPool)

2. 工作窃取work-stealing算法;

3. FIFO的双端队列ForkJoinPool.WorkQueue。

new ThreadPoolExecutor()

自定义线程池

package com.common.instance.test.config.pool;

import com.log.util.LogUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.concurrent.*;

/**
 * @description 线程池
 * @author tcm
 * @version 1.0.0
 * @date 2021/12/2 9:53
 **/
@Component
public class ExecutorHelper {

    // 单线程线程池
    @Bean("mySingleExecutor")
    public ExecutorService singleExecutor() {
        /*
            等价于:
            ExecutorService executorService = new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        */
        return Executors.newSingleThreadExecutor();
    }

    // 固定数量线程池
    @Bean("myFixedExecutor")
    public ExecutorService fixedExecutor() {
        /*
            等价于:
            ExecutorService executorService = new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
        */
        return Executors.newFixedThreadPool(10);
    }

    // 可缓存线程池
    @Bean("myCachedExecutor")
    public ExecutorService cachedExecutor() {
        /*
            等价于:
            ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
        */
        return Executors.newCachedThreadPool();
    }

    // 延迟执行线程池
    @Bean("myScheduledExecutor")
    public ExecutorService scheduledExecutor() {
        /*
            等价于:
            ScheduledExecutorService scheduledExecutorService = new ScheduledExecutorService(
                                      corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                                      new DelayedWorkQueue());
        */
        return Executors.newScheduledThreadPool(10);
    }

    // work-stealing线程池
    @Bean("myWorkStealingExecutor")
    public ExecutorService workStealingExecutor() {
        /*
            等价于:
            ExecutorService executorService = new ForkJoinPool(parallelism,
                                      ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                                      null, true);
            默认并行数:Runtime.getRuntime().availableProcessors():物理机核数
        */
        return Executors.newWorkStealingPool(10);
    }

    // 自定义线程池
    @Bean("myPoolExecutor")
    public ExecutorService myPoolExecutor() {
        // 定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                // 核心线程数
                10,
                // 最大线程数
                40,
                // 线程空闲时间
                60,
                // 空闲时间单位
                TimeUnit.SECONDS,
                // 队列大小
                new LinkedBlockingQueue<Runnable>(200),
                // 线程工厂创建线程
                new MyThreadFactory("myPoolExecutor-"),
                // 线程决绝策略
                new ThreadPoolExecutor.CallerRunsPolicy());

        // JVM关闭时,释放线程资源
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                // 关闭线程池,不接受新任务
                executor.shutdown();
                // 等待多久剩余的线程终止操作
                executor.awaitTermination(30, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                LogUtil.error("ExecutorHelper.myPoolExecutor()", "myPoolExecutor awaitTermination failed ", e);
            }
        }));
        return executor;
    }

}

2. 自定义线程工厂

package com.common.instance.test.config.pool;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description 定义线程工厂 - 参考DefaultThreadFactory
 * @author tcm
 * @version 1.0.0
 * @date 2021/12/2 16:45
 **/
public class MyThreadFactory implements ThreadFactory {

    // 池大小
    private final AtomicInteger poolNumber = new AtomicInteger(1);
    // 线程组
    private final ThreadGroup threadGroup;
    // 线程编号
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    // 线程前缀
    public  final String namePrefix;

    public MyThreadFactory(String name){
        SecurityManager s = System.getSecurityManager();
        threadGroup = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        if (null==name || "".equals(name.trim())){
            name = "pool";
        }
        namePrefix = name +"-"+
                poolNumber.getAndIncrement() +
                "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(threadGroup, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }

}

3. 调用

package com.common.instance.test.controller;

import com.common.instance.test.core.Response;
import com.common.instance.test.entity.WcPendantTab;
import com.common.instance.test.service.WcPendantTabService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * Tab菜单(WcPendantTab)表控制层
 *
 * @author tcm
 * @since 2021-01-14 15:02:08
 */
@Slf4j
@RestController
@RequestMapping("/tab")
@Api(tags = "活动tab测试")
public class WcPendantTabController {

    @Resource
    private WcPendantTabService wcPendantTabService;

    @Resource(name = "myPoolExecutor")
    private ExecutorService executor;

    @PostMapping("/testPoolExecutor")
    @ApiOperation("测试线程池")
    public Response<List<WcPendantTab>> testPoolExecutor(WcPendantTab tab){
        Response<List<WcPendantTab>> response = new Response<>();
        try {
            // 任务提交到线程池
            Future<Object> future = executor.submit(() -> wcPendantTabService.queryAll(tab));
            // 获取任务执行结果
            List<WcPendantTab> result = (List<WcPendantTab>) future.get();

            response.setData(result);
        } catch (Exception e) {
            e.printStackTrace();
            return Response.error();
        }
        return response;
    }

}

4. 其他测试

package com.cmmon.instance;

import org.junit.jupiter.api.Test;

import java.util.Objects;
import java.util.concurrent.*;

/**
 * @description 线程池测试
 * @author TCM
 * @version 1.0
 * @date 2022/4/17 17:32
 **/
public class ExecutorTest {

    ThreadPoolExecutor cachedThreadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    @Test
    public void cachedThreadPoolTest() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread() + ", cachedThreadPool.getPoolSize(): " + cachedThreadPool.getPoolSize());
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        Thread.sleep(6000);
        System.out.println("cachedThreadPool工作队列类型: " + cachedThreadPool.getQueue().getClass());
        System.out.println("6秒后的池线程数量: " + cachedThreadPool.getPoolSize());
        Thread.sleep(62000);
        System.out.println("62秒后的池线程数量: " + cachedThreadPool.getPoolSize());
    }

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
    @Test
    public void scheduledThreadPoolTest() throws InterruptedException {
        // 延迟任务执行
        scheduledThreadPool.schedule(() -> {
            System.out.println("延时执行任务");
        }, 10, TimeUnit.SECONDS);

        // 延时后固定周期执行
        // scheduleAtFixedRate(): 延时后以固定周期执行
        // scheduleWithFixedDelay(): 延时后以固定的延迟来执行(前次任务执行完毕后再延时period)
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            System.out.println("固定周期执行任务");
        }, 2, 1, TimeUnit.SECONDS);

        Thread.sleep(40000);
        System.out.println("main thread end");
    }

    final ConcurrentMap<String, Future<String>> concurrentMap = new ConcurrentHashMap<>();
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
    // 测试FutureTask
    @Test
    public void futureTaskTest() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            String taskName = executionTask("taskName" + i);
            System.out.println(taskName);
        }
    }

    private String executionTask(final String taskName) {
        while (true) {
            Future<String> future = concurrentMap.get(taskName);
            if (Objects.isNull(future)) {
                // 定义任务
                Callable<String> callable = new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return taskName;
                    }
                };

                // 定义一个FutureTask任务
                FutureTask<String> futureTask = new FutureTask<>(callable);
                future = concurrentMap.putIfAbsent(taskName, futureTask);
                if (Objects.isNull(future)) {
                    future = futureTask;
                    // FutureTask直接执行
                    futureTask.run();
                    // 提交给线程池执行
//                    fixedThreadPool.submit(futureTask);
                }
            }

            try {
                return future.get();
            } catch (Exception e) {
                e.printStackTrace();
                concurrentMap.remove(taskName, future);
            }
        }
    }


}

三、源码解析

1. 类图

JDK中ExecutorService有三种实现:

ThreadPoolExecutor:标准线程池

ScheduledThreadPoolExecutor:延迟任务线程池

ForkJoinPool:work-steeling模式线程池。池中的每个线程都创建一个队列,从而使用work-steeling算法(任务窃取)使得任务处理完成的线程从其他忙的线程队列中窃取任务。

2. Executor与ExecutorService源码对比

package java.util.concurrent;

/**
 * 1.该接口提交任务,使得提交任务与任务处理隔离,不显示创建线程;
 * 2.接受一个Runnable接口对象,并没有返回结果
 */
public interface Executor {

    /**
     * 提交任务,使得线程池中的线程处理任务逻辑
     * 注意:该方法并没有返回任务的处理结果
     * @param command Runnable任务
     * @throws RejectedExecutionException 线程池拒绝异常
     * @throws NullPointerException 空指针异常
     */

    void execute(Runnable command);
}
package java.util.concurrent;

import java.util.List;
import java.util.Collection;

/**
 * 1. 提供管理终止的方法,这些方法可跟踪异步线程执行任务的进度
 * 2. 关闭线程池有两种方法:shutdown()、shutdownNow()
 */
public interface ExecutorService extends Executor {

    /**
     * 关闭线程池
     * 1. 拒绝新的任务提交
     * 2. 正在执行的任务完成后关闭
     */
    void shutdown();

    /**
     * 立刻关闭线程池
     * 1. 拒绝新的任务提交
     * 2. 终止等待执行的任务,返回任务
     * 3. 终止正在执行的任务,返回任务
     */
    List<Runnable> shutdownNow();

    // 判定线程池是否关闭
    boolean isShutdown();

    // shutdown()后所有任务是否终止
    boolean isTerminated();

    // shutdown()后所有任务在timeout后终止
    boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;

    // 提交任务并返回任务处理结果
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

}
ExecutorExecutorService

1.execute()提交任务;

2.接受Runnable接口对象;

3.无返回任务处理结果

1.submit()提交任务;

2.接受Runnable、Callable接口对象;

3.返回任务处理结果;

4.继承Executor接口;

5.有控制线程池的方法,如shutdown()

3. CachedThreadPool详解

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

        如上代码所示,CachedThreadPool是按需创建新线程的线程池,即:corePoolSize=0。keepAliveTime=60L,空闲线程超过60秒后将会被终止。阻塞队列SynchronousQueue,它是一个没有容量的阻塞队列,即:每个插入操作必须等待另一个线程的对应移除操作,反之亦然。
        没有容量的SynchronousQueue作为线程池的工作队列,又是无界的。这意味着,如果主线程提交任务的速度高于线程池线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源
 

CachedThreadPool运行execute()示意图

        如上图所示,对步骤进行说明如下:

  • step1:执行offer()时,如果池中有空闲线程正在执行poll(),那么主线程执行offer操作与空闲线程执行poll操作配对成功,主线程把任务交给空闲线程执行;否则执行step2。
  • step2:池为空或当前没有空闲线程时,此时会创建一个新线程执行任务;
  • step3:step2创建新线程完成任务后,主线程60秒钟内提交了一个新任务,那么执行step1;否则,将终止空闲线程。
SynchronousQueue任务传递示意图

4. ScheduledThreadPoolExecutor详解

        ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,用来延迟或定期执行任务。它比Timer更强大。Timer对应的是单个后台线程,ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
            new DelayedWorkQueue());
}

        以上代码所示, keepAliveTime=0L,空闲线程会立即被终止。使用DelayedWorkQueue阻塞队列存储任务java.util.concurrent.ScheduledThreadPoolExecutor.ScheduledFutureTask,具有延时和周期执行任务。

        ScheduledFutureTask类主要有三个变量,如下。注意time相同时,按sequenceNumber小的优先出队

  • sequenceNumber:添加到阻塞队列的序列号
  • time:任务执行的绝对时间
  • period:执行任务的间隔周期

        如下步骤是ScheduledFutureTask的任务执行步骤:

  • 1)线程获取可以出队的任务(ScheduledFutureTask的time > 当前时间,说明当前任务可以出队);
  • 2)线程执行出队任务;
  • 3)线程修改出队任务的time变量(下次被执行的时间);
  • 4)完成任务后,放回队列。 

5. Future源码

package java.util.concurrent;

/**
 * 返回异步计算的结果
 * 检查计算是否完成;等待计算完成;结算完成获取结果
 */
public interface Future<V> {

    // 取消任务的执行
    boolean cancel(boolean mayInterruptIfRunning);

    // 任务完成前,将其取消,返回true
    boolean isCancelled();

    // 任务正常完成,返回true
    boolean isDone();

    // 等待任务执行完成,获取执行结果
    V get() throws InterruptedException, ExecutionException;
    
    // 等待多久后,获取执行结果(正常完成),否则抛出异常
    V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    
}

6. FutureTask详解

        FutureTask实现Future接口,还实现了Runnable接口。因此,FutureTask任务给Executor执行,也可以线程直接执行(FutureTask.run())。

FutureTask的状态迁移示意图

        

        上图所示是FutureTask的状态变化示意图,有下面三种状态:

  • 未启动:创建任务,但是调用run()之前;
  • 已启动:调用run()方法;
  • 已完成:任务正常结束、被取消cancel()、抛出异常。
个状态调用get()/cancel()的示意图

        上图所示,注意:任务未启动时,调用cancel()导致此任务永远不会被执行;任务已完成时,调用cancel()方法将返回false。

四、参考资料

Springboot学习笔记(一)-线程池的简化及使用 - 舒山 - 博客园

ThreadPoolExecutor使用详解 - WakamiyaShinobu - 博客园

深入分析java线程池的实现原理 - 简书

多线程——Executor、ExecutorService、Executors三者的区别 - 九零大叔芭蕉 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值