深入浅出java线程池

前言

Java1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦,只需把Task描述清楚,然后提交即可。至于这个Task是怎么被执行的,被谁执行的,什么时候执行的,就全部交给线程池管理。

小案例

废话不多说,先来一个小小案例。

import java.util.concurrent.*;

public class ExecutorTest {
    static ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws Exception {
        Future<String> future = executor.submit(new Task());
        String result = future.get();
        System.out.println(result);
    }

    static class Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(10);
            return "A";
        }
    }
}

代码很简单:初始化一个线程池,提交一个任务,主线程的future.get()会阻塞线程直到任务执行完成。

Executor框架成员

线程池实现框架中包含了一堆实现类,它们之间的关系如下,只有了解了各个类之间的关系,才能方便我们更好的理解线程池的实现。

从图中可以看到Executor、ExecutorService、ScheduledExecutorService定义线程池接口,ThreadPoolExecutor和ScheduledThreadPoolExecutor是线程池的实现,前者是一个普通的线程池,后者一个定期调度的线程池,Executors是辅助工具,用以帮助我们快速定义线程池。

参数

在初始化线程池时,不同的应用场景中,对参数的选择是很重要的,先来看看线程池的各个参数的含义:

1. workQueue:用来保存等待执行的任务的阻塞队列。

2. corePoolSize:线程池的基本大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使有其它空闲的线程,直到线程数达到corePoolSize时就不再创建,这时会把提交的新任务放到阻塞队列。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。

3. maximumPoolSize:线程池允许创建的最大线程数。如果阻塞队列满了,并且已经创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

4. threadFactory:创建线程的工厂。可以通过自定义线程工厂给每个线程设置有意义的名称。如guava提供的ThreadFactoryBuilder。

new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();

5. rejectedExecutionHandler:饱和策略。当阻塞队列满了且没有空闲的工作线程,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略在默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。不过,线程池提供了4种策略:

- AbortPolicy:直接抛出异常。

- CallerRunsPolicy:只用调用者所在的线程来运行任务。

- DiscardOldestPolicy:丢弃阻塞队列中最近的一个任务,并执行当前任务。

- DiscardPolicy:直接丢弃。

当然,也可以根据应用场景来实现RejectedExecutionHandler接口自定义饱和策略,如记录日志或持久化存储不能处理的任务。

6. keepAliveTime:线程活动保持时间。指工作线程空闲后,继续保持存活的时间。默认情况下,这个参数只有在线程数大于corePoolSize时才起作用。所以,如果任务很多,且每个任务的执行时间比较短,可以调大keepAliveTime,提高线程的利用率。

在初始化线程池时,对阻塞队列的选择也很重要,jdk中提供了以下几个阻塞队列:

- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO原则对元素进行排序。

- LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序元素,吞吐量通常要高于ArrayBlockingQuene。

- SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene。

- priorityBlockingQuene:具有优先级的无界阻塞队列。

Exectors

Exectors是java线程池的工厂类,通过它可以快速初始化一个符合业务需求的线程池,主要提供了以下几种便捷的方式:

1. newFixedThreadPool:创建一个指定工作线程数的线程池,其中参数corePoolSize和maximumPoolSize相等,阻塞队列基于LinkedBlockingQuene。

它是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());
}

2. newCachedThreadPool:创建一个可缓存工作线程的线程池(工作线程默认存活时间1分钟)。该线程池有以下特点:

This chapter requires login to view full content. You are viewing a preview.

Login to View Full Content

Course Curriculum

3

框架与 I/O:Spring、Netty 与 Web 容器

理解 Spring Boot 自动装配、AOP 与事务原理,掌握 Netty Reactor 模型及 Tomcat 连接处理机制,构建高内聚、易扩展的应用服务层。
4

高性能中间件:消息、缓存与存储

熟练运用 MySQL 索引/事务、Redis 缓存策略、Kafka/RocketMQ 消息可靠性,以及 ZooKeeper 分布式协调,搭建稳定、解耦的分布式数据底座。
6

云原生:容器化、可观测性与工程效能

通过 Docker/K8s 实现弹性部署,集成 Metrics/Logs/Traces 构建可观测体系,推动 DevOps 与自动化,让架构在云上持续交付与进化。