JAVA线程池

线程池类

Executor接口

其中就只有一个函数

void execute(Runnable command);

接口是一种规范,那么这个接口,就是要求其实现类,能够处理Runable的实例,换句话说,就是管理线程

ExecutorService

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    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;
}

ExecutorService继承了Executor,也是与线程池关系相当密切的一个接口,它一共定义了11个方法 ,加上execute一共12个方法
比起Execute,它不仅可以接收Runnable,还能接收Callable接口
这些方法起什么作用等下结合其实现类分析

Executors

这个类中有一些静态的工程方法和内部类,用于生成线程池级相关类

四个线程池实现类

java中常用的线程池有四个

CachedThreadPool
FixedThreadPool
ScheduledThreadPool
SingleThreadExecutor

通常,要创造他们的实例,都会使用Executors中的工厂方法

ExecutorService executor1 = Executors.newCachedThreadPool();
ExecutorService executor2 = Executors.newFixedThreadPool(8);
ExecutorService executor3 = Executors.newScheduledThreadPool(5);
ExecutorService executor4 = Executors.newSingleThreadExecutor();

拿到他们的类名称看看,究竟他们的实例属于哪个类

class java.util.concurrent.ThreadPoolExecutor
class java.util.concurrent.ThreadPoolExecutor
class java.util.concurrent.ScheduledThreadPoolExecutor
class java.util.concurrent.Executors$FinalizableDelegatedExecutorService

锁定目标之后,接下来就是逐个分析了

线程池实现

1.ThreadPoolExecutor

继承树:

上面的类信息表明,CachedThreadPool和FixedThreadPool实际上是属于同一个类,不同的应该只有创建时的参数不同而已
为了描述的简洁,我们跳过中间的查找过程,直接看ThreadPoolExecutor的最重要的构造器

七参构造器

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

构造器的方法体里面就是把这些值赋给类属性
可以看到这里一共7个参数,他们分别是:
int corePoolSize:线程池最小(核心)的线程数,如果allowCoreThreadTimeOut被标志为true,那线程数也可能小于这个数
注意这里的最小并不是要求线程数全程都大于等于corePoolSize,一开始是线程一个一个增加,到了corePoolSize这个数后,即使线程不用了,线程数也不会小于corePoolSize
int maximumPoolSize:线程池最大线程数
long keepAliveTime:线程数量大于corePoolSize时,多余线程的最大空闲存活时间,这个数字代表什么由下一个参数决定
TimeUnit unit:上一个参数的单位,TimeUnit是一个枚举类,建议查看源码
BlockingQueue workQueue:一个阻塞队列,用于存放线程
ThreadFactory threadFactory:线程工厂,用于创建线程
RejectedExecutionHandler handler:拒绝策略,用于线程池拒绝新的任务加入

除了这个构造器外,ThreadPoolExecutor还有一个五个参数的构造器

五(六)参构造器

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory //五参构造器没有这个参数
)

实际上,其内部也是调用了七个参数的构造器
而后面的两个实参是:

Executors.defaultThreadFactory()//五参构造器的默认值
defaultHandler

那么默认的defaultThreadFactory是什么样子的呢?

DefaultThreadFactory

Executors中的一个静态类,就是默认的线程工厂,除了构造器,之有一个方法:

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

简单来说,逻辑就是,这个线程不能是守护线程,默认的优先级是默认值5

defaultHandler

defaultHandler的实例是AbortPolicy这个类的实例
它的拒绝策略是直接抛出异常

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
            " rejected from " +
            e.toString());
}

相信大家早有耳闻,线程池一共四种拒绝策略:
这里我们就见到了第一种

FixedThreadPool的创建

这两个都是Executors中的工厂方法

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

FixedThreadPool:固定大小的线程池,其核心线程数量和最大核心数量都是nThreads,可以传入自定义的线程工厂,阻塞队列用的是LinkedBlockingQueue

CachedThreadPool的创建

这两个都是Executors中的工厂方法
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(),
            threadFactory);
}

CachedThreadPool:可缓存线程池,核心线程数为0,最大线程数为int的最大值,最大线程空闲存活时间是60秒,可以传入自定义的线程工厂,,阻塞队列用的是SynchronousQueue

FixedThreadPool和CachedThreadPool的区别

这两种线程池甚至使用了相同的构造器,所以显然只有构造器参数上的差别
数值的差别很好理解,但是为什么阻塞队列会有差别呢?

瞎JB乱猜开始了!
/**
因为我们可以认为,CachedThreadPool可以同时运行的线程数实际上是无限的
而阻塞队列存放的又是等待的任务
对于CachedThreadPool来说,任务自然不需要等待,有任务来,就会立马有线程接手
**/

2.ScheduledThreadPoolExecutor

定时的线程池

这个线程池的构造器参数和ThreadPoolExecutor完全一样,这里就不多说了
值得注意的是,默认的阻塞队列是DelayedWorkQueue,最大线程数是int最大值,而创建ScheduledThreadPoolExecutor需要指定的是corePoolSize,可选指定threadFactory
这个类的具体实现肯定与上一个有所不同,但是本博客还没到阅读源码的水平,等下实践给出例子体会一下应该就差不多了

3.FinalizableDelegatedExecutorService

这是Executor的内部类,其实就是一个ThreadPoolExecutor的代理类,只开放了部分方法,比如invokeall就没有开放
还重写了finalize方法,这是我以第一次见到有人这么干的

 protected void finalize() {
      super.shutdown();
 }

工厂方法是这么写的

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

4.其他线程池

上网搜索一下,java线程池的种类,大部分博客都会告诉你是4种,就是上面的4位
不知道这是那个版本jdk的说法了,实际上,在jdk8里面,Executors包含的工厂类就不止这4种.

单线程的延迟线程池,它也是一个被Executor内部类代理的线程池,核心是ScheduledThreadPoolExecutor,核心线程数为1,最大线程池为int最大值,阻塞队列是LinkedBlockingQueue

public static ScheduledExecutorService newSingleThreadScheduledExecutor();

这个线程池我就不了解是什么东西了,它实际上是一个ForkJoinPool,这些以后再去了解吧

public static ExecutorService newWorkStealingPool()

现在的线程池一共6种了,然而有些线程池仅仅以为构建的方式不同就分为不同的线程池,这种分类不便于我们理解
实际上,我们已知的线程池就是3种

ThreadPoolExecutor
ScheduledThreadPoolExecutor
ForkJoinPool

创建线程池不一定非要使用Executor,直接调用这些线程池的构造方法也可以
如果你够强,完全可以写一个自定义的线程池呀

补充

我们刚刚提到过一些知识点,但是还没有看清全貌
以下内容包含ThreadPoolExecutor和ScheduledThreadPoolExecutor,对于ForkJoinPool暂不分析

1.拒绝策略

拒绝策略就是线程池已经不能够接收新的任务时,对于新加入任务采取的措施

AbortPolicy

抛出异常

CallerRunsPolicy

由申请加入任务的线程自己执行

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

DiscardPolicy

直接丢弃,实际上就是什么都不做

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}

DiscardOldestPolicy

丢弃最老的
从这里看也不一定是最老的呀,这阻塞队列可不一定是先进先出的

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}

2.线程工厂

DefaultThreadFactory

默认的线程工厂,生成了一个非守护线程,优先级为5的线程

PrivilegedThreadFactory

这个类目前在我能力范围之外,大概与类加载器有关

总结

1.线程池种类

一般分类:
CachedThreadPool
FixedThreadPool
ScheduledThreadPool
SingleThreadExecutor

实际分类
ThreadPoolExecutor
ScheduledThreadPoolExecutor
ForkJoinPool

2.线程池参数(不包含ForkJoinPool)

int corePoolSize
int maximumPoolSize
long keepAliveTime
TimeUnit unit
BlockingQueue workQueue
ThreadFactory threadFactory
RejectedExecutionHandler handler

3.拒绝策略

AbortPolicy:抛异常
CallerRunsPolicy:交给原线程执行
DiscardPolicy:直接丢弃
DiscardOldestPolicy:丢弃最老的

部分方法

以下结论由测试得出,可能与事实有偏差

invokeAny

执行给定Callable集合中的某一个
FixedThreadPool总是执行第一个
CachedThreadPool大概率执行第一个
ScheduledThreadPool总是执行第一个
SingleThreadExecutor总是执行第一个

源码分析

以上总结了线程池是什么,下面分析一下具体的执行源码

成员变量

ctl

private final AtomicInteger ctl

这是一个表示状态的整数,高三位表示线程池运行状态,所谓的状态有:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。初始为RUNNING
后面所有未表示任务数量,不等同于线程数。

posted @ 2020-04-12 10:36  断腿三郎  阅读(252)  评论(0编辑  收藏  举报