手把手教小白创建线程池,并讲解ThreadPoolExecutor和Executors

前言

无论工作还是面试,都会接触到多线程,使用线程池的场景,笔者也是近期出去面试才了解到这个知识点的重要性。对于应届生和实习生的要求主要还是知道创建线程的方式,怎么创建线程池及怎么使用,除此之外你去面试人家都会问你原理的,所以本文就简单让大家了解线程池的创建,使用包括一些常见方法原理及参数介绍,适合三年内的同学观看。

正文

就目前啊,创建线程基本上已经不用继承Thread了,缺点:

         ◎new Thread新建对象,性能差。

         ◎线程缺乏统一的管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源或OOM(内存溢出)。

基本上都会使用线程池的方式了(JDK1.5推出的java.util.concurrent(简称JUC)并发工具包),优点

        ◎重用存在的线程,减少对象新建、消亡的开销。

        ◎线程总数可控,提高资源的利用率。

        ◎避免过多资源竞争,避免阻塞。

        ◎提供额外功能,定时执行、定期执行,监控等。

在java里使用线程池,离不开最核心的类ThreadPoolExecutor 。其实读者可以看看各大博客,讲线程池必讲的类,他们其实说的都差不多,但是对于初级学者其实不想了解太深,一次也记不住那么多,我就想知道最核心的方法以及使用时应该注意什么就可以。

ThreadPoolExecutor

所以,我只介绍ThreadPoolExecutor核心方法,帮助初学者打开线程池的大门。

扫描二维码关注公众号,回复: 5019477 查看本文章

这个类长这样

public class ThreadPoolExecutor extends AbstractExecutorService {……}

 

ThreadPoolExecutor类的属性介绍

选读内容

public class ThreadPoolExecutor extends AbstractExecutorService {
    // 线程池的控制状态(用来表示线程池的运行状态(整形的高3位)和运行的worker数量(低29位))
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 29位的偏移量
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 最大容量(2^29 - 1)
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    // 线程运行状态,总共有5个状态,需要3位来表示(所以偏移量的29 = 32 - 3)
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
    // 阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    // 可重入锁
    private final ReentrantLock mainLock = new ReentrantLock();
    // 存放工作线程集合
    private final HashSet<Worker> workers = new HashSet<Worker>();
    // 终止条件
    private final Condition termination = mainLock.newCondition();
    // 最大线程池容量
    private int largestPoolSize;
    // 已完成任务数量
    private long completedTaskCount;
    // 线程工厂
    private volatile ThreadFactory threadFactory;
    // 拒绝执行处理器
    private volatile RejectedExecutionHandler handler;
    // 线程等待运行时间
    private volatile long keepAliveTime;
    // 是否运行核心线程超时
    private volatile boolean allowCoreThreadTimeOut;
    // 核心池的大小
    private volatile int corePoolSize;
    // 最大线程池大小
    private volatile int maximumPoolSize;
    // 默认拒绝执行处理器
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
    //
    private static final RuntimePermission shutdownPerm =
        new RuntimePermission("modifyThread");
}    

关于ThreadPoolExecutor必读内容:这里着重讲解一下AtomicInteger类型的ctl属性,ctl为线程池的控制状态,用来表示线程池的运行状态(整形的高3位)和运行的worker数量(低29位)),其中,线程池的运行状态有如下五种

由于有5种状态,最少需要3位表示,所以采用的AtomicInteger的高3位来表示,低29位用来表示worker的数量,

即最多表示2^29 - 1。

/**
    * RUNNING    :    接受新任务并且处理已经进入阻塞队列的任务
    * SHUTDOWN    :    不接受新任务,但是处理已经进入阻塞队列的任务
    * STOP        :    不接受新任务,不处理已经进入阻塞队列的任务并且中断正在运行的任务
    * TIDYING    :    所有的任务都已经终止,workerCount为0, 线程转化为TIDYING状态并且调用terminated钩子函数
    * TERMINATED:    terminated钩子函数已经运行完成
    **/
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

ThreadPoolExecutor还有一个重要的地方就是他的构造方法。

这几个构造方法也是创建线程池调用的方法,需要大家了解的。

解释:该构造函数用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的 ThreadPoolExecutor。


public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
解释:该构造函数用给定的初始参数和默认被拒绝的执行处理程序创建新的 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
解释:该构造函数用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
解释:该构造函数用给定的初始参数创建新的 ThreadPoolExecutor,其他的构造函数都会调用到此构造函数。
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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

ThreadPoolExecutor 的execute函数

说明:当在客户端调用submit时,之后会间接调用到execute函数,其在将来某个时间执行给定任务,此方法中并不会直接运行给定的任务。此方法中主要会调用到addWorker函数。  

ThreadPoolExecutor 的addWorker函数

说明:此函数可能会完成如下几件任务

  ① 原子性的增加workerCount。

  ② 将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中。

  ③ 启动worker对应的线程,并启动该线程,运行worker的run方法。

  ④ 回滚worker的创建动作,即将worker从workers集合中删除,并原子性的减少workerCount。

ThreadPoolExecutor 的runWorker函数

 说明:此函数中会实际执行给定任务(即调用用户重写的run方法),并且当给定任务完成后,会继续从阻塞队列中取任务,直到阻塞队列为空(即任务全部完成)。在执行给定任务时,会调用钩子函数,利用钩子函数可以完成用户自定义的一些逻辑。

ThreadPoolExecutor 的shutdown函数

说明:此函数会按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。首先会检查是否具有shutdown的权限,然后设置线程池的控制状态为SHUTDOWN。

刚刚介绍了ThreadPoolExecutor中核心的参数及函数,下面介绍ThreadPoolExecutor最核心的内部类。

ThreadPoolExecutor有2115行代码,我想很少有人各个方法及参数倒背如流吧,背也没用,不如了解最核心的东西,给大家一个图。

ThreadPoolExecutor的核心内部类为Worker,其对资源进行了复用,减少创建线程的开销。

那么就讲一下这个核心内部类Worker,这个类是这样,

private final class Worker 
    extends AbstractQueuedSynchronizer 
    implements Runnable {省略……}

重点说明:Worker继承了AQS抽象类,其重写了AQS的一些方法,并且其也可作为一个Runnable对象,从而可以创建线程Thread。所以再有人问你使用线程池就不用创建线程了吗?你就怼他。

选读内容:可以看到Worker继承了AQS抽象类并且实现了Runnable接口,其是ThreadPoolExecutor的核心内部类。而对于AbortPolicy,用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException、CallerRunsPolicy,用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务、DiscardPolicy,用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务、DiscardOldestPolicy,用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。这些都是拒绝任务提交时的所采用的不同策略。

在Worker内部类属性介绍

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        // 版本号
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        // worker 所对应的线程
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        // worker所对应的第一个任务
        Runnable firstTask;
        /** Per-thread task counter */
        // 已完成任务数量
        volatile long completedTasks;
……省略代码
    }

必读内容:Worker属性中比较重要的属性如下,Thread类型的thread属性,用来封装worker(因为worker为Runnable对象),表示一个线程;Runnable类型的firstTask,其表示该worker所包含的Runnable对象,即用户自定义的Runnable对象,完成用户自定义的逻辑的Runnable对象;volatile修饰的long类型的completedTasks,表示已完成的任务数量。

Executors

这是第二部分。

在JDK帮助文档中,有如此一段话:

“强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。”

所以说为了方便开发,以及代码阅读考虑建议使用 Executors中提供的方法来创建线程,切记Executors是一个类,也是JUC下的,他和Executor没直接关系,甚至可以说一点关系都没有,别看名字很像,Executor是一个接口,只有一个方法,Executor他和ThreadPoolExecutor有关系,大家可以去看ThreadPoolExecutor继承和实现的关系。简单的说ThreadPoolExecutor的父接口是Executors。

下面直接讲解Executors创建线程池的几个方法。

一:ExecutorService newFixedThreadPool(int nThreads):固定大小线程池

说明: 1.如果线程池中有空闲线程,则利用起来;如果线程池中的所有线程都在执行任务,那么后续任务进入等待状态;
             直到有空闲的线程了,该线程再执行后续任务。

二:ExecutorService newSingleThreadExecutor():单线程。

说明:用来模拟单线程的线程池

三:ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收,也叫缓存线程池。

说明: 1.如果线程池中无空闲线程,则创建;有,则利用起来。

          2.线程总数固定。

          3.可以进行自动线程回收。

四:ExecutorService ScheduledThreadPool:调度线程池

说明:一般用于处理定时任务;与其类似的还有Timer;在实际做项目时,我们既不用ScheduledThreadPool,
         也不用Timer;而是用成熟的定时任务框架Quartz或Spring自带的定时调度。

总结一点,所有我刚刚提到的用Executors创建线程池的方法,底层都是用ThreadPoolExecutor实现的,只是参数不同罢了,

并且用的阻塞队列都是LinkedBlockingQueue,这是一个无界队列策略,原理大家可以看其他资料。

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

到这里关于ThreadPoolExecutor 重要的源码讲解,以及使用Executors创建线程大家应该都清楚了,

猜你喜欢

转载自blog.csdn.net/weixin_38003389/article/details/84942542