线程池执行过程
一、线程池的执行过程
定义一个目标任务类,一般是一个线程
public class Task implements Runnable { int k; public Task(int k) { this.k = k; } public void run() { for (int i = 0; i < 10; i++) { System.out.println("当前执行:" + k +"----"+Thread.currentThread().getName()); } } } |
定义调用者:一般用Executors工厂方法
public class ExecutorTest { public static void main(String args[]) { // ExecutorService executorService = Executors.newCachedThreadPool(); ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 100; i++) { Task task = new Task(i); executorService.execute(task); } } } |
结果:
��ǰִ�У�0----pool-1-thread-1 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�1----pool-1-thread-2 ��ǰִ�У�2----pool-1-thread-3 |
通过运行结果我们可以看到,当用newCachedThreadPool时,会创建很多线程,几乎是一个任务对应一个线程,而当用newFixedThreadPool时,如果任务数量不多,只会有3个线程在执行。
这里我们要引入一个概念:工作线程(workers,即线程池创建的线程,从ThreadPool源码中可以看到是一个HashSet)和任务线程(即目标任务,如实例中的Task类)。
我们看一下ThreadPool的源码:
Worker是ThreadPool的内部类,实现了Runnable接口。当我们的ThreadPool创建一个线程时创建的就是Worker。ThreadPool持有了一个HashSet来保存worker,名字叫workers。
现在我们介绍下主要的参数:
corePoolSize: 核心池大小。就相当于workers的大小。
maximumPoolSize: 最大池大小。相当于workers的极限值。
poolSize: 当前池大小。相当于当前workers的size。
当我们用ExecutorService.execute(new Task()) 这段代码来执行一个任务的时候,做了如下几件事。
Step1:execute方法:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { //加入第一次进入execute方法,poolSize=0,corePoolSize=3,会执行addIfUnderCorePoolSize方法,此方法保证如果小于corePoolSize则创建Worker if (runState == RUNNING && workQueue.offer(command)) { //如果workers超过了corePoolSize,addIfUnderCorePoolSize会返回false,就进入此循环,主要在workQueue.offer(command)方法上,这时就做入队操作,我们的阻塞队列就排上了用场。FixedThreadPool一般走这两步,对应LinkedBlockingQueue if (runState != RUNNING || poolSize == 0) // 当状态为非运行或者第一个核心线程执行 ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) // 如果队列不能往里加了,就到了这一步了,继续new 新的worker,直到达到上限,超过上限执行异常策略。cachedThreadPool的corePoolSize为0,因为SynchronousQueue的特性一般只会走这一步,前两步都不会走。 reject(command); // is shutdown or saturated } } |
从execute的源码我们分析出有三种情况:
1. 如果运行的线程少于corePoolSize,则Executor始终首选添加新的线程,而不进行排队,即使其他辅助线程是空闲的。
2. 如果运行的线程多于corePoolSize,小于maximumPoolSize,则首选将任务添加到队列,而不添加新的线程。
3. 如果无法将请求加入队列(如队列满),则继续创建新的线程,直到达到阀值(maximumPoolSize),这时会执行拒绝策略进行处理。
用一个通俗的例子可以很好理解这种思想:
一家软件公司,来了一批可盈利的任务,于是招了3个程序员大牛,每个大牛一次只能执行一个任务,当任务超过3个时,有一个队列(如一个程序员一天只能干10件任务,队列尺寸为27),这时来的任务加入工作队列,3个程序员就相当于3个worker线程,干完一个任务就去队列中取任务,直到队列处理完毕,3人下班回家,线程池关闭。然而当任务超过队列最大尺寸了,比如一下子来了50个任务,这时三个人已经达到极限了,公司只能再继续招人,但是招人也是有成本的,不能无限招,招多了公司的成本太高了,这就有了阀值的概念(maximumPoolSize),这时比如又招了2个人,达到了阀值(5),如果再有任务进来,公司就会拒绝。
上面的例子其实就是FixedThreadPool的翻版,而对于CachedPoolSize,其实只会走第三步,第二步极少出现(因为SynchronousQueue是一个单一生产者消费者模型,中间对静态资源进行了同步,offer和poll方法必须同时执行时,队列才会有值,这个我们后续再详细介绍)。返回例子,对于CachePoolSize,相当于这个软件公司来一个任务招一个程序员,
当任务执行完毕后去队列中查看,通过poll(time, TimeUnit.Second)方法获取任务,延迟60秒,这里就是缓存的地方。而LinkedBlokingQueue直接通过take方法获取任务。如果有任务,拿到之后执行任务,如果没有任务结束,程序员回家,线程池结束。(这个例子有点儿晦涩,我们下边源码分析就明白了)
Step:2:addIfUnderCorePoolSize方法(创建工作线程worker)
private boolean addIfUnderCorePoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < corePoolSize && runState == RUNNING) // 这里,调用addThread方法,创建线程 t = addThread(firstTask); } finally { mainLock.unlock(); } if (t == null) return false; t.start(); //线程执行 return true; } |
Step3:addThread
private Thread addThread(Runnable firstTask) { //1. 创建一个worker对象 Worker w = new Worker(firstTask); //2.通过线程工厂创建一个线程对象,Executors.newFixedThreadPool中都是用的Executors.defaultThreadFactory() Thread t = threadFactory.newThread(w); if (t != null) { w.thread = t; workers.add(w); //加入工作队列hashset:workers int nt = ++poolSize; // poolSize加一 if (nt > largestPoolSize) largestPoolSize = nt; } return t; } |
Step4: 通过默认线程工程构造线程
public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(),
0); //设置一些线程属性,设为普通用户线程,非守护 if (t.isDaemon()) t.setDaemon(false); //统一设置优先级 if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } |
Step5:Worker执行:step2中的start方法执行的是worker的run方法
public void run() { try { Runnable task = firstTask; firstTask = null; //循环执行,当前任务执行完毕后,设为null,去getTask,看看队列中有没有任务 while (task != null || (task = getTask()) != null) { runTask(task); //任务线程执行 task = null; } } finally { workerDone(this); } } } |
Step5.1:看下getTask方法
Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) //如果是关闭状态不再获取任务 return null; Runnable r; if (state == SHUTDOWN) // 正在关闭状态,把剩下的任务处理完 r = workQueue.poll(); else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果池中线程数大于核心线程数,任务较多,或允许核心线程池获取任务超时时,调用poll方法,SynchronousQueue走这一步 r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); Else //任务较少时,直接用take阻塞,BlockingQueue直接走这一步 r = workQueue.take(); if (r != null) return r; if (workerCanExit()) { if (runState >= SHUTDOWN) // Wake up others interruptIdleWorkers(); return null; } // Else retry } catch (InterruptedException ie) { // On interruption, re-check runState } } } |
Step5.2:runTask
private void runTask(Runnable task) { final ReentrantLock runLock = this.runLock; runLock.lock(); try { /* * Ensure that unless pool is stopping, this thread * does not have its interrupt set. This requires a * double-check of state in case the interrupt was * cleared concurrently with a shutdownNow -- if so, * the interrupt is re-enabled. */ if (runState < STOP && Thread.interrupted() && runState >= STOP) thread.interrupt(); /* * Track execution state to ensure that afterExecute * is called only if task completed or threw * exception. Otherwise, the caught runtime exception * will have been thrown by afterExecute itself, in * which case we don't want to call it again. */ boolean ran = false; beforeExecute(thread, task); try { task.run(); //注意这里,直接就是方法调用 ran = true; afterExecute(task, null); ++completedTasks; } catch (RuntimeException ex) { if (!ran) afterExecute(task, ex); throw ex; } } finally { runLock.unlock(); } } |
Step6:workerDone
void workerDone(Worker w) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; //累加完成任务数 workers.remove(w);//移除worker if (--poolSize == 0) tryTerminate(); } finally { mainLock.unlock(); } } |
基本的流程就是:创建线程池——》创建新线程或加入队列——》
工作线程(workers)开始执行——》 调用目标任务的run方法——》循环查看队列,获取任务——》loop。。