简述线程池

线程池

不需要如何创建线程,只需要关心业务逻辑。线程池统一创建,管理,销毁。
线程可以重用,避免创建销毁的资源开销。

阿里规范关于线程池的几点要求

2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:自定义线程工厂,并且根据外部特征进行分组,比如机房信息。
public class UserThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
// 定义线程组名称,在 jstack 问题排查时,非常有帮助
UserThreadFactory(String whatFeaturOfGroup) {
 namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {
 String name = namePrefix + nextId.getAndIncrement();
 Thread thread = new Thread(null, task, name, 0, false);
 System.out.println(thread.getName());
 return thread;
}
}
3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问
题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

线程池相关类图和几个重要的类

Executors工具类

JDK内置的几种线程池可以使用Executors工厂类创建,扮演工厂的角色,也可以创建特定功能的线程池。

newSingleThreadExecutor

有且只有一个线程在工作,适合任务顺序执行,缺点是不能充分利用CPU多核的性能

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

newFixedThreadPool 定长线程池

最大容量默认是Integer.MAX_VALUE,没有超时时间设置,适合能估算出多少核心线程的场景。
用于负载比较大的服务器,为了资源合理分配,限制线程数量

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

newCachedThreadPool 缓存线程池

核心线程数是0,最大是Integer最大值,保活时间60S,这种队列不会存任务,智慧转发。适合执行大量的,轻量级任务。

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

newScheduleThreadPool 周期定时执行

类似于定时器

public static ScheduleExecutorService newScheduleThreadPool(int corePoolSize){
    return new ScheduleThreadPoolExecutor(corePoolSize);
}

Executor接口

只有一个execute方法,用来替代创建或者启动线程的放大。

Thread t = new Thread();
executor.execute(t);

ExecutorService接口

继承自Ececutor接口,提供了管理终止线程的方法,以及可为最总一个或者多个异步任务执行状况而生成的Future的方法,完善了整个执行任务的生命周期。

void shutDown();//结束
List<Runnable> shutDownNow();//马上结束
boolean isShutDown();//是否结束了
invokeAll();//执行所有
invockAny();//执行任意
submit();//提交到线程池,什么时候执行有线程池决定
boolean isTerminated();//是不是整体执行完了
boolean awaitTermination(long Timeout,TimeUnit unit) throws InterruptedException ()//等着结束,超时就返回false

SheduledExecutorService接口

扩展了ExecutorService接口并增加了schedule方法。调用schedule方法可以指定在延时指定间隔后执行一个Runnable或者Callable任务。提供了两个方法scheduleAtFixedRate(),scheculeWithFixedDelay()。

ThreadPoolExecutor

继承自AbstractExecutorService,也实现了ExecutorService接口。

ThreadPoolExecutor源码分析

几个重要字段

//(高三位)表示线程状态,(低三位)表示线程个数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING,0));

//线程个数掩码位数,对应平台下Integer的二进制位数-3剩余位数表示线程的个数
private static final int COUNT_BOTS=Integer.SIZE - 3;

//线程最大个数,低29位
private static final int CAPACITY = (1 << COUNT_BITS)-1;

//线程状态
private static final int RUNNING = -1 << COUNT_BITS;   //高三位 111
private static final int SHUTDOWN = 0 << COUNT_BITS;   //高三位 000
private static final int STOP = 1 << COUNT_BITS;       //高三位 001
private static final int TIDYING = 2 << COUNT_BITS;    //高三位 010
private static final int TERMINATED = 3 << COUNT_BITS; //高三位 011
/**
*ctl是对线程池的运行状态(runState)和有效线程数量(workerCount)进行控制的一个字段。
*workerCount的上限值大概是5亿。
*状态和线程数是通过位运算来提高效率的。
*/

核心参数

corePoolSize 线程池基本大小

当提交一个新的任务到线程池,线程池都会创建一个县的线程来执行此任务,无论线程池是否有空闲状态的线程。这种情况会持续到当任务数量大于线程池的基本线程数量大小时就不再创建了。也就是说新任务来的时候先检查是否超过核心线程数,没有就创建,有就放进等待队列。

调整线程池的大小

《java并发编程实战》一书中给出一个公式:
N(线程数) = N(CPU数核心数目)* N(期望的CPU利用率,在0-1之间)* (1+W/C)
W/C是等待时间和计算时间的比率
具体问题具体分析,实际应该估算后实测为准

maximumPoolSize 线程池允许的最大线程数量

如果队列满了,并且已经创建的线程数小于最大线程数,线程池会创建新的线程执行任务。本质上核心线程和非核心线程是没有区别的,也没有标识标记是否是核心线程。线程池只是用已有线程数和核心线程数以及最大线程数作比较决定下一步的策略。

keepAliveTime 线程保活时间

线程池允许的线程空闲时间,当线程池的线程数量大于核心线程数的时候,没有新的任务继续提交,那么核心线程外的线程会等待一个keepAliveTime 的时间,超过等待时间就会销毁。
如果这个参数是0就会导致多余的线程在任务执行完之后立即终止。

TimeUnit 保活时间单位

可以使用TimeUnit 时间单位设置

ThreadFactory 线程工厂

用于设置创建新线程的工厂。默认使用Executors.defaultThreadFactory()来创建线程。使用默认的工厂创建线程时,会使用新建的具有相同的NORM_PRIORITY优先级并且是非守护线程,同时设置了线程的名称。

runnableTaskQueue 任务队列

用于保存在等待执行任务的阻塞队列。等待任务队列用于存储当核心线程都在忙时,继续新增的任务,核心线程在处理完当前任务后,会去等待队列拉取任务继续执行。一般要求是线程安全的阻塞队列,容量根据业务选择。

ArrayBlockingQueue:基于数组的阻塞队列,按照FIFO原则进行排序

LinkedBlockingQueue:基于链表的阻塞队列,按照FIFO原则进行排序,吞吐量高于ArrayBlockingQueue。Executors.newFixedThreadPool()使用了这个队列。

SynchronousQueue:一个不存储元素的阻塞队列,每一个插入必须等待另外一个县城移除操作,否则插入状态一直处于阻塞状态。吞吐量高于LinkedBlockingQueue,Executors.nweCacheThreadPool使用了这个队列。

priorityBlockingQueue:一个具有优先级的无限阻塞队列.

RejectExecutionHandler 拒绝策略(饱和策略)

本身是一个Java接口,当队列和线程池都满了,需要一种策略处理新来的任务。Java提供了四种实现,也可以自定义实现。
默认策略是丢弃策略

AbortPolicy:直接抛异常

CallerRunsPolicy:只在调用者所在的线程来运行任务

DiscardOldestPolicy:丢弃队列里最早的一个任务

DiscardPolicy:直接丢弃,没有任何其他操作

自定义:实现RejectExecutionHandler 接口

ScheduledThreadPoolExecutor

继承自ThreadPoolExecutor,也实现了SheduledExecutorService接口。

Fork/Join框架

java7提供了fork/join框架用于并行执行任务。fork将一个大人物分割成若干个小任务,join汇总小任务的结果得到大任务的最终结果。

CompletableFuture是用fork/join框架实现的

实现:线程池中的每个线程都有自己的工作队列(ThreadPoolExecutor是所有的线程公用一个工作队列,所有的线程在一个队列中取任务),当自己工作队列中的人物完成后,会从其他线程偷一个任务执行,充分利用资源。

work-stealing 工作窃取算法:从其他队列偷任务

ForkJoinPool

是Fork/Join框架中的任务调度器,和ThreadPoolExecutor一样实现了自己的线程池,提供了三种调度子任务的方法:

execute

异步执行任务,无返回结果

invoke/invokeAll

异步执行指定任务,等待完成后才返回结果

submit

异步执行指定任务,立即返回一个Future对象

ForkJoinTask

Fork/Join框架中实际的执行任务类,有两种实现,继承实现即可。

RecursiveAction

用于无结果返回的子任务

RecursiveTask

用于有结果返回的子任务

Callable

Callable类似于Runnable,只是比Runnable多了返回值


package java.util.concurrent;

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Future、FutureTask

Future用来封装线程未来的返回值。可以是callable执行结束的返回值。也可以是ExecutorService的submit提交的异步方法执行结束后的返回值。

posted @ 2020-04-10 13:46  凿石头的小石匠  阅读(269)  评论(0编辑  收藏  举报