线程池是Java开发中常用的功能,在日常开发经常用到,那对于其的实现原理是否了解?下面就线程池的原理做个简单的分析。就从最简单的使用开始来分析具体实现和领悟JDK 实现的基本思想。
- 最简单的线程池使用方式
ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new Runnable() { @Override public void run() { System.out.println("executor"); } });
上面是最简单的线程池使用方式,那线程池是如何运行该提交的执行任务的呢?下来分两步来分析:
- 线程池的初始化,下面是Executors类里面的初始化线程池的方法:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
根据上面newFixedThreadPool方法来看,只是返回了一个ThreadPoolExecutor对象,注意的是整个JDK实现的线程池关键的一个类就是ThreadPoolExecutor,包括调度线程池最终也是初始化的ThreadPoolExecutor对象。那我们来看看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.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
由上面的构造方法来看,先是对关键参数做了检查,满足条件之后实际就是将客户端传入的参数或者相关默认参数初始化成ThreadPoolExecutor对象返回给客户端。
初始化一个线程池有几项比较关键的参数
属性 | 含义 |
corePoolSize | 线程池的最小大小,也就是初始化大小。 |
maximumPoolSize | 线程池的最大大小,对于当前线程池处理的任务超过此数目,则任务只能等待有其他工作线程释放之后才能继续处理新的提交任务。 |
workQueue |
工作队列,当客户端向线程池提交的任务数量大于核心线程池大小时会先存入此队列中。 如果客户端向线程池提交的任务数量小于核心数量,则不会向队列中添加,而是直接启动一个新的线程执行提交的任务。 |
threadFactory | 线程工厂。顾名思义就是创建线程的工厂对象,使用者可以自定义自己的线程工厂,不指定则使用JDK默认的线程工厂。第二步,提交工作线程 |
第二步,提交工作线程
service.execute(new Runnable() { @Override public void run() { System.out.println("executor"); } });
执行上述代码为什么会执行run()方法呢?下面来分析下server.execute()方法。
- 比较当前工作的worker线程是否小于核心/最小线程池大小,这里是我们代码中指定的10,如果小于则添加到Worker线程池工作任务数组中,并直接诶开启新的线程执行任务。
-
如果提交的任务数量大于核心线程池大小,这个时候工作队列就派上用处了,会把提交的任务提交的工作队列中,等待空闲线程去工作队列中去任务执行。
-
如果加入工作队列失败,则尝试添加到Worker线程组中,并启动新的线程执行任务。如果当前线程超过了最大线程池大小,则会拒绝此任务的执行,任务执行失败。
总体来看,java线程池的实现很大一部分就是依赖的工作阻塞队列(BlockingQueue),默认的工作队列有
LinkedBlockingQueue SynchronousQueue 等多种。线程池能够做到线程的高度复用实际也是依赖阻塞队列优秀的阻塞、唤醒机制。
下面我们来分析阻塞队列的实现机制。