Java并发之线程池详解

1,001 阅读6分钟

欢迎关注公众号:五小竹

什么是线程池

我们在应用中,通过new Thread().start()的方法创建执行一个线程来执行任务,执行完后线程关闭,整个过程中,线程的创建和关闭需要花费时间,当线程数量多的时候,会占用很多CPU源。

所以,为了减少频繁创建和关闭线程的开销。我们可以让创建好的线程复用。如同数据库连接池,我们在进行数据库的查询的时候,需要建立连接和销毁连接,为了避免连接和销毁的开销,我们可以通过数据库连接池(如c3p0,druid等)来管理数据库连接。线程池也是这样,在“池”中,有可用的线程,当需要线程来执行任务时候,直接从线程池中取,执行完后,不用关闭,可以留着给新的线程复用。

线程池的实现

下面是ThreadPoolExecutor的UML类图。 sKB0zj.md.jpg

顶层接口是Executor

只有一个方法execute()

public interface Executor {
    //定义 execute 方法来执行任务
    void execute(Runnable command);
}

ExecutorService接口

对Executor进行了扩展,丰富了对任务的执行和管理的功能。 提供了管控线程池的方法,比如停止线程池的运行;还提供了给一个或多个异步任务生成Future的方法,

// 关闭线程池
void shutdown();
//是否已经关闭,true 已关闭,false 未关
boolean isShutdown();
// 所有的任务是否都已经终止
boolean isTerminated();
//请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException;
// 提交一个 Runnable 有返回值的任务用于执行,并返回一个表示该任务的 Future。
<T> Future<T> submit(Callable<T> task);
// 提交没有返回值的任务
Future<?> submit(Runnable task);
//执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException;
//  执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。其余未完成的任务将被取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException;

AbstractExecutorService抽象类

实现了ExecutorService接口的一些方法如 submit、invokeAny、invokeAll 等方法,使得在使用线程池的时候,只用关注任务的实现,将任务提交给线程池来处理就行了。execute方法并没有实现,这个交由ThreadPoolExecutor了

实现类ThreadPoolExecutor

这是线程池的核心类,它维护了线程池的生命周期和核心参数。下面再详细说明。

ForkJoinPool

ForkJoinPool是Java7提供的,可以用来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果的分而治之框架。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承了ScheduledThreadPoolExecutor,通过实现ScheduledExecutorService接口,对ThreadPoolExecutor进行了扩展,支持延时后执行异步任务或者周期性执行任务。

线程池核心ThreadPoolExecutor

线程池的状态

线程池内部维护了自己的生命周期,它将运行状态(rs)和线程数量 (wc)两个关键参数的维护放在了一起 通过一个 32 位的原子整数来表示。其中高 3 位表示rs,低 29 位表示wc

  private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //32-3=29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 29个1
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    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;

    //计算当前运行状态,将c 的低 29 位修改为 0,就得到了线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //计算当前线程数量:将整数 c 的高 3 为修改为 0,可以得到线程池中的线程数
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
状态描述
RUNNING接受新的任务,处理workQueue中的任务
SHUTDOWN不接受新的任务提交,但会处理workQueue中的任务
STOP不接受新的任务提交,不再处理workQueue中的任务,中断正在执行任务
TIDYING所有的任务都终止了,workCount 为 0
TERMINATEDterminated() 方法结束后,线程池的状态就会变成这个状态
  • RUNNING -> SHUTDOWN:当调用了 shutdown() 后,会发生这个状态转换

  • RUNNING -> STOP 调用 shutdownNow()

  • SHUTDOWN -> TIDYING:当任务队列和线程池都清空后。

  • STOP -> TIDYING:任务队列清空后,线程池中工作线程数量为0,发生这个转换

  • TIDYING -> TERMINATED:在terminated()方法执行完后

线程池的核心参数

参数描述
corePoolSize核心线程数
maximumPoolSize最大线程数,线程池允许创建的最大线程数
workQueue任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务
keepAliveTime线程池中的线程空闲时间。
unitkeepAliveTime 参数的时间单位。
threadFactory执行程序创建新线程时使用的工厂。
handler由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

线程池运行规则

  1. 如果 线程数 < corePoolSize ,则创建并启动一个线程来执行新提交的任务
  2. 如果线程数 >= corePoolSize && 线程数<maxumumPoolSize,则添加到workQueue.
  3. 如果workQueue已满,并且线程数<maximumPoolSize,则创建新的线程运行任务
  4. 如果workQueue已满,并且线程数>=maximumPoolSize,则拒绝任务。
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
       
        int c = ctl.get();
        // 如果线程数<corePoolSize,那么直接添加一个 worker 来执行任务,
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程数>=corepoolsize,线程池处于 RUNNING 状态,把这个任务添加到workQueue 中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 如果这时候线程池不 RUNNING了,就移除已经入队的这个任务,并执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
         // 如果 workQueue 队列满了,线程数<maximumPoolSize,则创建新线程
    // 如果失败,说明线程数已经达到 maximumPoolSize,执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

创建线程池

通过Executors类提供的工厂方法可以创建线程池。

  • Executors.newWorkStealingPool 创建持有足够线程的线程池,支持给定并行度。创建ForkJoinPool。 Java8引入。
  public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
        //Runtime.getRuntime().availableProcessors获取CPU数量,作为线程池的并行度
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
  • Executors.newCachedThreadPool 需要的时候创建新的线程,同时可以复用之前创建的线程的线程池

  • Executors.newScheduledThreadPool 支持定时和周期性执行的线程池,与newCachedThreadPool的区别在于它不回收工作线程

  • Executors.newSingleThreadExecutor 生成单线程的线程池

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • Executors.newFixedThreadPool 一个固定大小的线程池 ,它的核心线程数=最大线程数,不存在空闲线程。keepAliveTime=0。
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

线程池的拒绝策略

当线程池的任务缓存队列满了,且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务了。

  1. AbortPolicy 线程池的默认拒绝策略,丢弃任务,抛出RejectedExecutionException。
  2. DiscardPolicy 丢弃任务,不抛异常
  3. DiscardOldestPolicy 丢弃任务队列最早的任务,重新提交当前任务
  4. CallerRunsPolicy 只要线程池没有被关闭,那么由提交任务的线程自己来执行这个任务。

钩子方法

addWorker 方法的功能就是增加一个线程, 使用 new Worker (firstTask) 新建Worker,再使用 start () 执行 ,其实调用到的是runWorker()方法。

runWorker(Worker w)

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    //help gc
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            //锁住 worker
            w.lock();
            // 线程池 stop 中,但是线程没有到达中断状态,帮助线程中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                //执行 before 钩子函数
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    //执行任务
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    //执行 after 钩子函数 
                    afterExecute(task, thrown);
                }
            } finally {
                //任务执行完成,计算解锁
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }

在runWorker()方法中,ThreadPoolExecutor提供了两个钩子函数beforeExecute(wt, task)和afterExecute(task, thrown);其实还有一个terminated()。 我们可以利用这几个钩子方法扩展我们的线程池。

public class ExecutorDemo {
	public static void main(String[] args) {
		ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0l,
				TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) {
			@Override
			protected void beforeExecute(Thread t, Runnable r) {
				System.out.println("开始执行:" + r);
			}

			@Override
			protected void afterExecute(Runnable r, Throwable t) {
				System.out.println("执行完成:" + r);
			}

			@Override
			protected void terminated() {
				System.out.println("Thread Pool terminated!");
			}
			
		};
		executorService.submit(() -> {
			System.out.println("Task Running!!!");
		});
		executorService.shutdown();
	}

}
执行结果
开始执行:java.util.concurrent.FutureTask@212aa004
Task Running!!!
执行完成:java.util.concurrent.FutureTask@212aa004
Thread Pool terminated!

使用线程池的注意点

  • 线程池的大小会对应用的性能产生影响。因此需要合理的设置线程池参数
  • FixedThreadPool 和 SingleThreadPool允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 CachedThreadPool 和 ScheduledThreadPool 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 基于以上原因,在阿里巴巴Java开发手册中强制要求线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。