Java线程池和Executor框架

目录

线程池的好处

线程池的实现原理

Runnable和Callable接口

Executor框架

ThreadPoolExecutor

ScheduledThreadPoolExecutor

Future


线程池的好处

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

  3. 提高线程的可管理性。使用线程池可以对线程进行统一分配、调优和监控。

线程池的实现原理

Java的线程即是工作单元,也是执行机制。从JDK 1.5开始,把工作单元和执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供。

Runnable和Callable接口

Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果。除了可以自己创建实现Callable接口的对象外,还可以使用工厂类Executors.callable()来把一个Runnable包装成一个Callable,这里使用的是适配器模式。

适配器模式是将一个类的接口转换成客户希望的另一个接口。Adapter模式使得由于接口不兼容而不能一起工作的那些类可以一起工作。

@FunctionalInterface
public interface Runnable {
    void run();
}

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

public class Executors {
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null) {
            throw new NullPointerException();
        } else {
            return new Executors.RunnableAdapter(task, result);
        }
    }

    public static Callable<Object> callable(Runnable task) {
        if (task == null) {
            throw new NullPointerException();
        } else {
            return new Executors.RunnableAdapter(task, (Object)null);
        }
    }

    private static final class RunnableAdapter<T> implements Callable<T> {
        private final Runnable task;
        private final T result;

        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }

        public T call() {
            this.task.run();
            return this.result;
        }

        public String toString() {
            return super.toString() + "[Wrapped task = " + this.task + "]";
        }
    }
}

Executor框架

在HotSpot VM的线程模型中,Java线程(Java.lang.Thread)被一对一对映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系统也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU。

在上层,Java多线程程序通常会把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。应用程序通过Excutor框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。

任务的两级调度模型

Executor框架主要由3大部分组成如下。

  1. 任务。包括被执行任务需要实现的接口:Runnable接口和Callable接口。

  2. 任务的执行。包括任务执行机制的核心接口Executor,以及继承自Excutor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadExecutor)。

  3. 异步计算的结果。包括接口Future和实现Future接口的FutureTask类。

ThreadPoolExecutor

ThreadPoolExecutor构造函数有如下几个参数

  1. corePoolSize:核心线程池大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。

  2. maximumPoolSize:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会创建新的线程执行任务。如果使用了无界队列,那么会使这个参数无效果。

  3. keepAliveTime:空闲线程等待时间。为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。

  4. unit:keepAliveTime的单位

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

  6. ThreadFactory:用于创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

  7. handler:饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么新提交的任务将交由饱和饱和策略处理。JDK1.5开始Java提供了以下4种策略。这四中策略是实现了RejectedExecutedHandler的ThreadPoolExecutor的内部类

  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy只用调用者所在的线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                              BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

ThreadPoolExecutor执行execute()提交一个新任务到线程池时,线程池的处理流程如下。

  1. 如果当前线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

  2. 如果运行的线程等于或对于corePoolSize,则将任务加入BlockingQueue。

  3. 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,创建新的线程需要获取全局锁)。

  4. 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectExecution()方法。

execute()执行示意图:

ThreadPoolExecutor的execute()执行示意图

注:ThreadPoolExecutor的总体设计思路,在线程完成预热之后(当前运行线程大于等于corePoolSize),几乎所有的execute()都是步骤2,避免获取全局锁。

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。等待队列用的是DelayQueue,DelayQueue是一个无界队列,所以maximumPoolSize参数没有意义。

ScheduledThreadPoolExecutor执行任务分为两步。

  1. 调用scheduledThreadPoolExecutor的scheduledAtFixedRate()方法或scheduleWithFixedDelay()方法时,会向DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask。

  2. 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

ScheduledThreadPoolExecutor任务传递示意图

DelayQueu封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面(时间早的任务将先被执行)如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(time相同时,先提交的任务会先执行)。ScheduledFutureTask主要包含3个成员变量,如下。

//这个任务被添加到ScheduledThreadPoolExecutor中的序号
private final long sequenceNumber;

//这个任务将要被执行的具体时间
private volatile long time;

//任务执行的间隔周期
private final long period;

ScheduledThreadPoolExecutor执行某个周期任务分为4步。

  1. 线程1从DelayQueue中获取已到期的ScheduledFuturTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间。

  2. 线程1执行这个ScheduledFutureTask。

  3. 线程1修改ScheduledFutureTask的time变量为下次将要执行的时间。

  4. 线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

ScheduledThreadPoolExecutor任务执行步骤

Future

Future接口和实现Future接口的FutureTask类,代表异步计算的结果。FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给Executor执行,也可以由调用线程直接执行(FutureTask.run())。根据FuntureTask.run()方法被执行的时机,FutureTask可以处于下面三种状态。

  1. 未启动。FutureTask.run()方法还没有被执行之前,FuntureTask处于未启动状态。当创建一个FutureTask,且没有执行FutureTask.run()方法之前,这个FuntureTask处于未启动状态。

  2. 已启动。FutureTask.run()方法被执行的过程中,FutureTask处于已启动状态。

  3. 已完成。FutureTask.run()方法执行完后正常结束,或被取消(FutureTask.cancel(...)),或执行FutureTask.run()方法是抛出异常而异常结束,FutureTask处于已完成状态。

FutureTask的状态迁移示意图

当FutureTask处于未启动或已启动状态时,执行FutureTask.cancel()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。

当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务;当FutureTask处于已启动状态时,执行FutureTask.cancel(false)将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);当FutureTask处于已完成状态时,执行FutureTask.cancel(...)方法将返回false。

FuntureTask的get和cancel的执行示意图

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值