线程池ThreadPoolExecutor原理及使用分析

日常开发中, 如果我们需要使用线程执行某些业务, 在阿里开发手册中有明确约定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。下面我们来分析以下线程池ThreadPoolExecutor的原理及优点

线程池的优点

  • 可以控制线程并发数量
  • 减少线程的创建和销毁过程,提升效率
  • 可以对线程进行管理

构造方法及参数说明

corePoolSize 核心线程个数
maximumPoolSize 最大工作线程个数,如果核心线程不够用,阻塞队列已满,则会增加工作线程数至此值,提高效率
keepAliveTime 增加的工作线程,如果超过这个时间后一直空闲,则取消该线程
unit 空闲时间单位
workQueue 阻塞队列,如果核心线程已满,使用队列存放等待线程 阻塞队列源码
threadFactory
handler 如果等待队列已满,并且工作线程数达到了maximumPoolSize,则再添加线程时,会调用此接口中rejectedExecution方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler); // 使用默认的线程工厂和拒绝策略
}
public ThreadPoolExecutor(int corePoolSize,
                      	  int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler); // 只使用默认的拒绝策略
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler); // 只使用默认的线程工厂
}
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;
}

4种拒绝策略

CallerRunsPolicy 交由调用线程完成执行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

AbortPolicy 直接抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

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);
    }
}

submit和execute区别

execute只能接收Runnable类型参数并且没有返回值
submit可以接受Runnable类型和Callable类型参数,可以获取到线程的返回值

public void execute(Runnable command) {
	if (command == null)
		throw new NullPointerException();
	
	int c = ctl.get(); // 获取ctl的值, 其中高3位表示状态, 低29位表示工作线程worker数量
	if (workerCountOf(c) < corePoolSize) { // worker数量低于核心线程数量
		if (addWorker(command, true)) // 创建核心线程worker
			return;
		c = ctl.get();
	}
	if (isRunning(c) && workQueue.offer(command)) { // 执行到此说明核心线程已满, 添加到阻塞队列
		int recheck = ctl.get(); // 再次取ctl值, 校验状态
		if (! isRunning(recheck) && remove(command)) // 如果不是运行中状态, 则从阻塞队列中删除
			reject(command); // 调用拒绝策略
		else if (workerCountOf(recheck) == 0) // 如果worker的数量为0
			addWorker(null, false); // 添加空的worker
	}
	else if (!addWorker(command, false)) // 执行到此说明阻塞队列添加失败, 可能已经满了, 创建弹性线程
		reject(command); // 如果没有成功, 则执行拒绝策略
}

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

如何关闭

shutdownNow执行后会立即中断执行的线程,并返回阻塞队列中等待的线程。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 标记线程池状态为STOP
        advanceRunState(STOP);
        // 中断执行中的线程
        interruptWorkers();
        // 将阻塞队列中的线程以list形式返回给调用者
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted(); // 调用已启动线程的interrupt方法
    } finally {
        mainLock.unlock();
    }
}

shutdown执行后

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 标记线程池状态为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 终止那些还没有运行的线程
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) { // 没被终止 并且 能获取到锁的线程
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

Executors构造线程池及坑点

常用的创建线程方法:
Executors.newFixedThreadPool();
Executors.newCachedThreadPool();
Executors.newSingleThreadExecutor();

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

可以看到线程队列使用的参数类型大都是LinkedBlockingQueue,而LinkedBlockingQueue默认长度是Integer.MAX_VALUE,如果阻塞队列过大,堆空间不断上升,可能会导致OOM。

这点在阿里开发手册也有相关约束:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

如何合理的设置线程池参数

线程池最关键的参数是maximumPoolSize,线程最大并发数量,如果设置过大,并发量太高,导致CPU、内存、磁盘资源用尽;如果设置过小,队列太长,不能最大限度使用硬件资源。目前并发线程数有如下通用方式(通过资源占用方式区分),以供参考:
CPU密集型
1.线程多偏于CPU运算,此时maximumPoolSize应为CPU核心数+1
IO密集型
2.可以根据生产服务运行情况,不断调整参数,达到最优,一般值为CPU核心数*2
3.通用配置CPU核心数/(1-阻塞系数) 阻塞系数返回0.8~0.9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值