线程池个人理解和实现

进程和线程

        进程可以理解为数据结构和内存空间上的,不具备运行能力;应用程序之所以能够运行OS在创建进程的同时为应用程序创建了一个主线程。

        线程可以理解为程序中的一条执行路径,同时线程也是cpu调度的最小单位,没有线程存在进程就会结束OS回收资源。

为什么使用线程池

        在不使用线程池之前当有多个任务需要处理时,只要有任务进入就需要创建新的线程来执行,这样看似也不错但是我们知道线程的创建时很耗费资源的且平凡的创建线程会影响整个系统的性能,因此我们设想加入有一组已经创建好的线程处于挂起状态,当有任务来时就唤醒其中一个进行任务的处理,执行完成后又进入挂起,这样就避免了运行中平凡创建线程带来的性能问题,可能有人会问线程是并发执行的吗?这个要看cpu的物理核心或者超线程数量,至少单核心下线程也是在一个时间片内得到执行。因此线程的出现就是为了解决程序运行中平凡创建线程带来的性能问题。

线程池的构成

       线程池管理者,线程池既然是一组线程组成,那么首先会有个线程来进行管理,例如线程组的创建,任务的管理,线程的调度,线程和任务的数量并不是无限大的因此设置一个阈值能够使线程池更好的运行。

       工作线程,即处于待命的线程,有任务执行时唤醒没有时挂起。

       任务即要做的事情,在程序中就是一个接口,因此任务具体要做的事情就不归线程池来处理了从能任务和线程池互补影响,线程池只管调用接口,具体的事情接口自己知道。

       任务队列,线程池中的线程数量是有限的不可能每个任务能够立刻得到执行,因此需要一个队列来保存这些任务等待线程池来调度这些任务;当然任务也并不是有多少就保存多少这样内存会被耗尽最终内存溢出,因此这就出现了饱和策略。

jdk中的线程池

       Executor框架是java中的线程池实现。Executor是最顶层的接口定义,它的子类和实现主要包括ExecutorService,ScheduledExecutorService,ThreadPoolExecutor,ScheduledThreadPoolExecutor,ForkJoinPool等。其结构如下图所示:

Executor:Executor是一个接口,其只定义了一个execute()方法:void execute(Runnable command);,只能提交Runnable形式的任务,不支持提交Callable带有返回值的任务。
ExecutorService:ExecutorService在Executor的基础上加入了线程池的生命周期管理,我们可以通过ExecutorService#shutdown或者ExecutorService#shutdownNow方法来关闭我们的线程池。ExecutorService支持提交Callable形式的任务,提交完Callable任务后我们拿到一个Future,它代表一个异步任务执行的结果。关于shutdown和shutdownNow方法我们需要注意的是:这两个方法是非阻塞的,调用后立即返回,不会等待线程池关闭完成。如果我们需要等待线程池处理完成再返回可以使用ExecutorService#awaitTermination来完成。
shutdown方法会等待线程池中已经运行的任何和阻塞队列中等待执行的任务执行完成,而shutdownNow则不会,shutdownNow方法会尝试中断线程池中已经运行的任务,阻塞队列中等待的任务不会再被执行,阻塞队列中等待执行的任务会作为返回值返回。
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;
}

四种类型的线程池

FixedThreadPool 定长线程池

它是一种固定大小的线程池;
corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;
由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效

CachedThreadPool 可缓存线程池

它是一个可以无限扩大的线程池;
它比较适合处理执行时间比较小的任务;
corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

SingleThreadExecutor  单一线程池

它只会创建一条工作线程处理任务;

采用的阻塞队列为LinkedBlockingQueue;

ScheduledThreadPool  可调度的线程池

它用来处理延时任务或定时任务。

线程池流程图

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章