Java(线程池)

1.线程

1.1 线程

  • 线程 在Java中是一个对象,更是操作系统的资源,线程的创建和销毁都需要时间。如果 创建时间+销毁时间>执行任务时间,就很不划算了。
  • Java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈是1M,这个栈空间需要从系统内存分配,线程过多会消耗很多的内存。
  • 操作系统频繁的切换线程上下文,影响性能。

1.2 线程池

  • 管理并复用线程、控制最大并发数
  • 实现任务线程队列缓存策略和拒绝机制
  • 实现某些与时间相关的功能,如定时任务执行、周期执行等
  • 隔离线程环境,比如交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要打;因此通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免各服务线程相互影响。

1.3 概念

  • 线程管理池: 用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务
  • 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环执行任务
  • 任务接口: 每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务执行的状态等
  • 任务队列: 用于存放没有处理的任务,提供缓冲机制

2. 线程池 API

2.1接口定义及实现类

  • Executor: 最上层接口,定义了执行任务的方法 execute
  • ExecutorService: 接口,继承了Executor,拓展了Callable、Future、关闭方法
  • ScheduledExecutorService:接口, 继承了ExecutorService,增加了定时任务相关的方法
  • RunnableFuture ,接口,继承自Future 接口,在线程池中,用于将Runnable 的任务包装成FutureTask,然后提交到线程池中
  • ThreadPoolExecutor: 实现类,基础标准的线程池实现
  • ScheduledThreadPoolExecutor: 实现类,继承了ThreadPoolExecutor,实现了ScheduledExecutorService 中相关的定时任务方法
  • Executors: 工具类,里面的方法都是静态方法,用于生成ThreadPoolExecutor 的一些实例

继承关系图:
在这里插入图片描述

2.2 Executor 接口

/* 
 * @since 1.5
 * @author Doug Lea
 */
public interface Executor {
    void execute(Runnable command);
}

它仅提供了一个 #execute(Runnable command) 方法,用来执行已经提交的 Runnable 任务。
Executor 接口无法满足执行结果获取、当前活动线程个数、以完成任务个数等需求。

2.3 ExecutorService

ExcutorService 拓展了一些方法,为了让提交执行模式更加完善,比如提交任务的:submit、invokeAll、invokeAny,但拓展这些提交方法最终调用的还是 excute 方法

public interface ExecutorService extends Executor {

    /**
     * 关闭线程池,会执行完以前提交的任务,不接受继续提交新任务
     */
    void shutdown();

    /**
     * 关闭线程池,试图停止所有正在执行的活动任务,不接受继续提交新任务
     * 暂停处理正在等待的任务,并返回等待执行的任务列表
     * 它和前面的方法相比,加了一个单词“now”,
     * 区别在于它会去停止当前正在进行的任务
     */
    List<Runnable> shutdownNow();

    /**
     * 如果此执行程序已关闭,则返回 true。
     */
    boolean isShutdown();

    /**
     * 如果关闭后所有任务都已完成,则返回 true
     */
    boolean isTerminated();

    /**
     * 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    // ========== 提交任务 ==========

    /**
     * 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future
     */
    <T> Future<T> submit(Callable<T> task);

    /**
     * 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future
     */
    Future<?> submit(Runnable task);

    /**
     * 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表
     */
    <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;
}

2.4 FutureTask

FutureTask 类通过RunnableFuture 间接实现了Runnable 接口,所以每个Runnable 通常都先包装成 FutureTask,然后调用 executor.execute(Runnable command) 将其提交给线程池
在这里插入图片描述
Future 接口:

public interface Future {

    /**
     * 试图取消对此任务的执行
     * 如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。
     * 当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。
     * 如果任务已经启动,则 mayInterruptIfRunning 参数确定是否以中断的方式停止任务
     */
    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;
}

Runnable 的 void run() 方法是没有返回值的,所以,如果需要的话,会在 submit 中指定第二个参数作为返回值:

<T> Future<T> submit(Runnable task, T result);

其实到时候会通过这两个参数,将其包装成 Callable。它和 Runnable 的区别在于 run() 没有返回值,而 Callable 的 call() 方法有返回值,同时,如果运行出现异常,call() 方法会抛出异常。

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

2.5 AbstractExecutorService

AbstractExecutorService 抽象类派生自 ExecutorService 接口,然后在其基础上实现了几个实用的方法,这些方法提供给子类进行调用。
这个抽象类实现了 invokeAny 方法和 invokeAll 方法,这里的两个 newTaskFor 方法也比较有用,用于将任务包装成 FutureTask。定义于最上层接口 Executor中的 void execute(Runnable command) 由于不需要获取结果,不会进行 FutureTask 的包装。

需要获取结果(FutureTask),用 submit 方法,
不需要获取结果,可以用 execute 方法。

2.6 ThreadPoolExecutor

ThreadPoolExecutor 是 JDK 中的线程池实现,这个类实现了一个线程池需要的各个方法,它实现了任务提交、线程管理、监控等等方法。
Worker 是一个内部类,因为 Doug Lea 把线程池中的线程包装成了一个个 Worker,翻译成工人,就是线程池中做任务的线程。所以任务是 Runnable(内部变量名叫 task 或 command),线程是 Worker。
Worker 又用到了抽象类 AbstractQueuedSynchronizer。

2.6.1 属性

/**
 * 高 3 位是 线程池状态
 * 后 29 位是 工作线程个数
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 这里 COUNT_BITS 设置为 29(32-3),
//意味着前三位用于存放线程状态,后29位用于存放线程数
private static final int COUNT_BITS = Integer.SIZE - 3;

// 000 11111111111111111111111111111
// 这里得到的是 29 个 1,也就是说线程池的最大线程数是 2^29-1=536870911
// 以我们现在计算机的实际情况,这个数量还是够用的
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// 运算结果为 111跟29个0:111 00000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS;
// 000 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;


// Packing and unpacking ctl
// 获得线程池状态
// 将整数 c 的低 29 位修改为 0,就得到了线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 获得工作线程个数
// 将整数 c 的高 3 为修改为 0,就得到了线程池中的线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 通过线程池状态 和 工作线程个数 得到 ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }

这里定义了五种工作状态:

  • RUNNING: 处于RUNNING 状态的线程池能够接收新任务,以及对新添加的任务进行处理
  • SHUTDOWN: 处于SHUTDOWN 状态的先成功不可以接受新任务,但是可以对已添加的任务进行处理
  • STOP: 处于STOP 状态 的线程池不接受新任务,不处理已添加的任务,中断正在执行的任务。
  • TIDYIND: 当所有的任务已终止,ctl记录的“工作线程数”为0,线程池会变为 TIDYING 状态。当线程池变为TIDYING 状态会执行钩子函数terminate()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
  • TERMINATED:线程池彻底终止的状态。

在这里插入图片描述

2.6.2 构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize : 线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
  • maximumPoolSize: 线程池中允许的最大线程数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果了。
  • keepAliveTime: 线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间:keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效。
  • unit: keepAliveTime的单位。TimeUnit
  • workQueue : 用来保存等待执行的任务的阻塞队列,等待的任务必须实现Runnable接口。我们可以选择如下几种:
  • threadFactory: 用于设置创建线程的工厂,通过newThread() 方法提供创建线程的功能,newThread() 方法创建的线程都是 “非守护线程” 而且 “线程优先级都是Thread.NORM_PRIORITY ”。
  • handler: RejectedExecutionHandler,线程池的拒绝策略。所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略。当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。

workQueue 阻塞队列:

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO
  • LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。
  • SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作,反之亦然。
  • PriorityBlockingQueue:具有优先界别的阻塞队列。

线程池提供了四种拒绝策略:

  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;
  • 当然我们也可以实现自己的拒绝策略,例如记录日志等等,实现RejectedExecutionHandler接口即可。

2.6.3 FixedThreadPool

可重用固定线程数的线程池,即 核心线程数 = 最大线程数,队列是无界阻塞

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

2.6.4 SingleThreadExecutor

只有一个线程的线程池,即 核心线程数 = 最大线程数 = 1,队列是无界阻塞

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

2.6.5 CachedThreadPool

不限制线程数的线程池,即 核心线程数 = 0 , 最大线程数是 Integer.MAX,队列是不存储元素的 SynchronousQueue

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值