J.U.C——线程池专题

【置顶】
如果有什么错误或者建议,欢迎留言指出
本文内容是对各个知识点的转载整理,用于个人技术沉淀,以及大家学习交流用



主要讨论以下问题:

  • 认识Java线程池
  • 线程池的种类,区别,和使用场景
  • 线程池的工作流程
  • 线程池几个参数的理解
  • 分析线程池的实现原理
  • 线程池中 工作线程的调度过程。
  • 线程池如何调优,线程池的最大线程数目根据什么确定


什么是线程池

java.util.cuncurrent.Executors 提供了一个 java.util.concurrent.Executor 的接口实现,用于创建线程池。
多线程记住主要解决处理器单元内多个线程执行的问题,它可以显著减少CPU的闲置时间,增加CPU的吞吐量。

一个线程池包括以下四个基本部分:
1)线程池管理器(ThreadPool):用于创建并管理线程池,包括,创建线程池,销毁线程池,添加新任务。
2)工作线程(PookWorker):线程池的中的线程,在没有任务提交的时候处于等待状态,可以循环的执行任务。
3)任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完成以后的收尾工作,以及任务的执行状态。
4)任务队列(TaskQueue):存放待处理的任务,提供一种缓冲机制。

线程池技术的关键是缩短线程的创建和销毁所消耗的时间,复用已经创建的线程,减少创建的线程数,从而提高服务性能。



线程池的种类

JUC提供的基本线程池实例

java.util.cuncurrent.Executors 下,Java提供了四种基本的线程池(还有其他的暂时不看了):
1)newFixedThreadPool:固定线程数量的线程池(核心线程数=最大线程数),提交任务到线程最大数量后,就把任务放到无界队列中(LinkedBlockingQueue)。
缺点:工作队列大小无限制,但是可能造成工作队列提交任务过多,资源耗尽。
源码如下:

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

// 自定义ThreadFactory
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue(),
                                      threadFactory);
}


2)newCachedThreadPool:可缓存线程池(核心线程数=0,最大线程数=Integer.MAX_VALUE)。可以回收空闲线程(默认设定60秒过期时间),当有任务提交时,启动新线程执行。工作队列使用同步队列(SynchronousQueue)。
缺点:最大线程数无限制,当提交任务过多时,可能造成创建大量线程,导致资源耗尽。
源码如下:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
}

// 自定义ThreadFactory
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue(),
                                      threadFactory);
}


3)newSingleThreadExecutor:单线程化的线程池,只会用唯一的工作线程来执行任务,保证所有被提交的任务按照FIFO的顺序执行。
工作队列是一个阻塞队列(LinkedBlockingQueue)
缺点:依然是阻塞队列没有限制大小,如果任务提交过多,可能导致队列中任务阻塞过多导致资源耗尽。

源码如下:

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

// 自定义ThreadFactory
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue(),
                                    threadFactory));
}


4)newScheduledThreadPool:大小无限制的线程池,支持定时和周期性的执行线程。由ScheduledThreadPoolExecutor创建(其又继承自ThreadPoolExecutor)。
源码如下:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
 }

// 自定义ThreadFactory
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

// 单线程化的ScheduledThreadPool
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
}

// 单线程化的ScheduledThreadPool 自定义ThreadFactory
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1, threadFactory));
}


5)newWorkStealingPool:返回拥有多个任务队列的线程池,结合JDK1.7中引入的 Fork/Join 框架来理解。(TODO)
源码如下:


public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
}

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
}



线程池的参数

主要参数如下:
corePoolSize:int型,核心线程数
maximumPoolSize:int型,最大线程数
keepAliveTime:long型,线程的存活时间(默认非核心线程空闲时会被回收)
unit:TimeUnit型,线程存活时间单位
BlockingQueue:工作队列
ThreadFactory:自定义工作线程工厂
RejectExecutionHandler:拒绝策略Handler

线程存活时间和时间单位:如果线程数大于核心线程,那么空闲的线程会在存活一定时间之后被回收。直到活跃线程数等于核心线程数。
也可以通过设置 allowCoreThreadTimeOut(boolean value) 来指定允许核心线程被回收。

工作队列
工作队列是一个 BlockingQueue,只要是实现了阻塞队列的都可以。具体可以包括 ArrayBlockingQueueLinkedBlockingQueueSynchronousQueueDelayQueue 等等。不同的工作队列有不同的运行特性。

拒绝策略
拒绝策略 RejectedExecutionHandler
是一个接口,主要作用是,当线程池拒绝任务之后,执行该接口定义的方法来执行拒绝以后的行为。
可以自定义行为,当然,Java也提供了几种策略供选择:
1)new ThreadPoolExecutor.CallerRunsPolicy() 重试执行当前任务,但会交由调用者线程来执行该任务。

2)new ThreadPoolExecutor.AbortPolicy() 抛出RejectedExecutionException 异常

3)new ThreadPoolExecutor.DiscardPolicy() 直接抛弃任务,不做任何响应动作

4)new ThreadPoolExecutor.DiscardOldestPolicy() 抛弃线程池中最后要执行的那个任务,并执行新传入的任务。



线程池的工作流程

工作流程如图

PS:具体流程可以参考源码:
java.util.concurrent.ThreadPoolExecutor.execute(Runnable command)

主要分为四个步骤:
1)如果线程池中的线程数小于核心线程数,则会尝试创建一个新线程,该任务被创建的新线程立即执行。

2)如果线程数已经达到了核心线程数,但是工作队列没有满,则该任务会尝试加入到工作队列中。

3)如果工作队列也满了,那么会尝试创建新线程,直到达到了最大线程数。

4)如果达到了最大线程数,那么又有新任务提交进来时,则会抛出异常或者执行拒绝策略。


向线程池中提交任务
主要有两种方式:
1)execute:由Executor 接口定义,接受Runnable的任务,返回void。ExecutorService接口继承该接口,然后由ThreadPoolExecutor实现。
简单来说,就是不需要获取执行结果的,使用该方法。

2)submit:由ExecutorService 接口定义,有三个重载的方法,分别支持接受Runnable和Callable的任务。
AbstractExecutorService 的实现中,接收task,包装为一个RunnableFuture类型,并交由execute执行,并返回该RunnableFuture对象。
具体重载方法中,接受Runnable的重载方法会返回null。接受Callable参数的会把执行结果包装为 Future 对象返回。
submit 的主要优势在于,方便Exception处理。如果希望捕获task中出现的异常并作出相应处理的话,可以通过Future.get() 抛出的异常,来进行相应的处理。这一点 execute 是无法做到的,也是使用 submit 最主要的作用。

至于 Future 以及 FutureTask,会单独开一篇用于复习。



线程池的实现原理



参考资料:
Java-线程池专题 (美团面试题)
Java线程池原理详解
Java中线程池——ThreadPoolExecutor原理
Java多线程——ThreadPooExecutor详解
Java线程池种类和使用场景
Java线程池调优
Java中线程技术及其优化
Java并发编程——线程池的使用
线程池submit与execute的区别

你可能感兴趣的:(J.U.C——线程池专题)