《Java并发》线程池实现详解

1. 概述

**什么是线程池?**从字面含义来看,线程池是指管理一组同构工作线程的资源池。线程池是与工作队列(Work Queue)密切相关的,其中在工作队列种保存了所有等待执行的任务。工作者(Worker Thread)的任务就是不断的从工作队列种获取任务,然后执行任务。当工作队列种没有任务时,工作者就会阻塞,直到工作队列中有任务了就取出来,然后继续执行。

**为什么要使用线程池?**线程的创建和销毁涉及到系统调用等等,是比较消耗CPU资源的。如果线程本身执行的任务时间短,创建和销毁线程所耗费的资源就会占很大比例。

假设一个服务器完成一项任务所需时间为:T1创建线程时间,T2在线程中执行任务的时间,T3销毁线程时间。如果:T1+T3远大于T2,则可以采用线程池,实现对线程的复用,以提高服务器性能。

使用线程池有哪些优势?

  • 降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  • 提高响应速度:两个线程如果被分配到不同的CPU内核上运行,任务可以真实的并发完成。
  • 提高线程的可管理性:可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存而降低系统的稳定性。

2. 继承关系

3.1 Executor

java.util.concurrent.Executors提供了一个java.util.concurrent.Executor接口的实现用于创建线程池,Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService

public interface Executor {
    void execute(Runnable command);
}

Executor用来实现异步框架,它提供了一种标准的方法将任务的提交过程与执行过程解耦开来,使用Runnable来表示任务,其中execute()用于提交没有返回值的任务。通过使用Executor,可以实现各种调优、管理、监视、记录日志、错误报告和其他功能。

3.2 ExecutorService

为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,ExecutorService继承了Executor,添加了生命周期管理的方法,并扩展对Callable任务的支持,返回Future来提供给调用者。

public interface ExecutorService extends Executor {
// 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
boolean awaitTermination(long timeout, TimeUnit unit);
// 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);
// 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);
// 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks);
// 执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);
// 如果此执行程序已关闭,则返回 true。
boolean isShutdown();
// 如果关闭后所有任务都已完成,则返回 true。
boolean isTerminated();
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
void shutdown();
// 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
List<Runnable> shutdownNow();
// 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<?> submit(Runnable task);
// 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
<T> Future<T> submit(Runnable task, T result);
}

涉及到的生命周期的处理方法有:

  • shutdown():平缓地关闭线程,调用该方法后,将不再接收新的任务,同时等待已经提交的任务执行完成,包括那些还未开始执行的任务。
  • shutdownNow():粗暴地关闭线程,调用该方法后,将尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务。
  • awaitTermination():阻塞等待线程结束。
  • isTerminated():判断线程是否结束。
  • isShutdown():判断线程是否关闭。

在ExecutorService关闭后提交的任务将由拒绝执行处理器(Rejected Execution Handler)来处理,它会抛弃任务,或者使得execute方法抛出一个未检查的Rejected ExecutionException来等待ExecutorService到达终止状态,或者通过调用isTerminated()来轮询ExecutorService是否已经终止。通常是在调用awaitTermination()之后立即调用shutdown(),从而产生同步地关闭ExecutorService的效果。

3.3 AbstractExecutorService

AbstractExecutorService实现了 ExecutorServiceExecutor接口的基本方法,ThreadPoolExecutor和ForkJoinPool继承AbstractExecutorService可以减少实现的复杂度。

3.4 ThreadPoolExecutor

在《阿里巴巴Java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,而是使用ThreadPoolExecutor创建线程池,避免资源耗尽的风险,优化资源的开销。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
字段名称描述
corePoolSize线程池核心线程数(平时保留的线程数),创建后不会关闭的线程数量,如果设置了allowCoreThreadTimeout后,空闲时间超时后还是会关闭。执行任务时,没有达到核心线程数的话,是会直接创建新的线程。
maximumPoolSize规定线程池最多只能有多少个线程(worker)在执行。当核心线程数和任务队列也都满了,不能添加任务的时候,这个参数才会生效。如果当前有效工作线程数量小于最大线程数量,会再创建新的线程。
keepAliveTime超出corePoolSize数量的线程的保留时间。
unitkeepAliveTime的时间单位。
workQueue任务队列,为阻塞队列BlockingQueue的实现。线程池会先满足corePoolSize的限制,在核心线程数满了后,将任务加入队列。但队列也满了后,线程数小于maximumPoolSize,线程池继续创建线程。
threadFactory线程创建工厂,可以用来配置线程的命名、是否是守护线程、优先级等等。
handler拒绝处理器,为RejectedExecutionHandler的实现类。当任务队列满负荷,已经达到最大线程数,把新加入的任务交给这个handler 进行处理。线程池默认使用AbortPolicy(直接抛弃)策略,直接抛出异常。
allowCoreThreadTimeout核心线程的空闲时间也要进行超时限制,也就是keepAliveTime的限制。如果配置为true后,所有的线程空闲时间超时后,都会进行线程退出操作。

拒绝处理器(RejectedExecutionHandler)饱和策略:

  • AbortPolicy(中止策略):直接抛弃。
  • CallerRunsPolicy(调用者运行策略):用调用者的线程执行任务。
  • DiscardPolicy(丢弃策略):抛弃当前任务。
  • DiscardOldestPolicy(弃老策略):抛弃队列中最久的任务。

4. 四种主要线程池

  • SingleThreadPool:单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务。
  • FixedThreadPool:固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。
  • CachedThreadPool:可缓存线程池,当处理任务所需的线程数超过线程池大小,那么就会回收超过60秒未被使用的线程,如果当前线程池中没有可用的线程,则会创建一个新的线程添加到线程池中。
  • ScheduledThreadPool:定时调度线程池。
  • FixedThreadPoolSingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致OOM 。
  • CachedThreadPoolScheduledThreadPool : 允许的创建线程数量为Integer.MAX_VALUE ,可能会创建大量的线程,从而导致OOM 。

5. 实现Runnable和Callable的区别

如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于Runnable接口不会返回结果但是Callable接口可以返回结果。

6. 执行execute()和submit()的区别

  • execute():用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  • submit():用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过Future对象可以判断任务是否执行成功,并且可以通过Future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

7. 使用示例

public class ExecutorDemo {
    class ThreadHandler implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "正在执行....");
        }
    }
    public static void main(String[] args) {
        ExecutorDemo executorDemo = new ExecutorDemo();
        executorDemo.singleThreadPool();
        executorDemo.fixedThreadPool();
        executorDemo.cachedThreadPool();
        executorDemo.scheduledThreadPool();
    }
    // singleThreadPool
    public void singleThreadPool() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        ThreadHandler t1 = new ThreadHandler();
        ThreadHandler t2 = new ThreadHandler();
        executorService.execute(t1);
        executorService.execute(t2);
        executorService.shutdown();
    }
    // fixedThreadPool
    public void fixedThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ThreadHandler t1 = new ThreadHandler();
        ThreadHandler t2 = new ThreadHandler();
        executorService.execute(t1);
        executorService.execute(t2);
        executorService.shutdown();
    }
    // cachedThreadPool
    public void cachedThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        ThreadHandler t1 = new ThreadHandler();
        ThreadHandler t2 = new ThreadHandler();
        executorService.execute(t1);
        executorService.execute(t2);
        executorService.shutdown();
    }
    // scheduledThreadPool
    public void scheduledThreadPool() {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        scheduledThreadPoolExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "正在执行....");

            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值