在使用线程的,我们常使用线程池,线程的复用可以减少线程的开销,很好的提高cpu资源利用率。这篇文章信息量会很大,做好准备哦~ java源码系列。
有对线程池没有直观感觉的同学可以看看这篇文章。在jdk8中,从ThreadPoolExecutor从发,来逐步讲解它的源码实现。
1.线程池定义初始值
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; //容量限制在2^29-1 //线程池中线程有五种状态 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; //所有的任务都已经停止了,工作线程的数目为0,线程在转换到改状态是会调用中断方法 private static final int TERMINATED = 3 << COUNT_BITS; //调用了中断方法 private static int runStateOf(int c) { return c & ~CAPACITY; } //获取线程运行的状态 private static int workerCountOf(int c) { return c & CAPACITY; } //当前工作的线程 private static int ctlOf(int rs, int wc) { return rs | wc; } //获取ctl
private static boolean runStateAtLeast(int c, int s) { //比较最后的状态值 return c >= s; } //判断线程是否正在执行 private static boolean isRunning(int c) { return c < SHUTDOWN; }
初始化了一定基本变量及方法,下面会经常使用到。
2.执行方法
public void execute(Runnable command) { //提交线程的执行方法 if (command == null) //如果放入线程池中的线程为null,会包空指针异常 throw new NullPointerException(); int c = ctl.get(); //获取clt的当前值,这个值会经常被修改 if (workerCountOf(c) < corePoolSize) { //工作线程个数小于核心线程个数 if (addWorker(command, true)) //新建一个线程来执行任务 return; c = ctl.get(); //添加线程失败,重新回去ctl的值,下面isRunning方法需要判断线程是否是运行状态 } if (isRunning(c) && workQueue.offer(command)) { //如果线程的个数大于核心线程池的个数,并且当前线程是运行状态,它就会被放到缓存队列中 int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) //如果放到队列成功了,但是线程池被关了,需要移除掉该线程,没问题的话就拒绝这个线程的执行 reject(command); else if (workerCountOf(recheck) == 0) //如果工作线程的数量变成0了,添加空的任务,之后其实知道从队列中取任务时会跳过null的 addWorker(null, false); } else if (!addWorker(command, false)) //队列满了,如果小于最大线程池的大小,可以新建线程执行任务,如果大于最大线程池大小,就会拒绝任务 reject(command); }
总结,总有有四步:
第一步,判断线程是不是为空,如果是空的话,就抛出空指针异常。
第二步,如果线程的数量小于核心线程池的个数,创建新的线程执行任务。
第三步,如果线程的数量大于核心线程的数量,就将线程放到缓存队列中。
第四步,缓存队列的满了,还没有到达最大核心线程池的大小,这个时候就也是创建线程执行任务,如果大于最大的核心线程池的大小,那么就拒绝任务的执行。这里所说执行任务的线程就是Worker.
2.1添加Woker线程
先来看下Worker的定义
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { private static final long serialVersionUID = 6138294804551838833L; final Thread thread; Runnable firstTask; volatile long completedTasks; Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); } ....... }继承了aqs,同时实现了Runable,说明是一个线程.
private boolean addWorker(Runnable firstTask, boolean core) { retry: //retry: 不明白是什么的可以看这篇文章 for (;;) { int c = ctl.get(); //获取到最新的ctl int rs = runStateOf(c); //查看当前线程的状态 //如果线程的状态是关闭,第一个任务时空,队列是空,直接返回添加任务失败 if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || //需要小于容量 wc >= (core ? corePoolSize : maximumPoolSize)) //core为true,工作线程要小于核心线程数,core为false,工作线程数要小于最大线程数目。不然,也放回添加任务失败 return false; if (compareAndIncrementWorkerCount(c)) //如果任务个数添加成功,就跳出循环,这个用到了CAS,不明白的可以看这篇文章 break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) //运行状态不等的话,需要不断的重试,直到成功 continue retry; // else CAS failed due to workerCount change; retry inner loop } } //下面方法是真正的创建线程,上面干的事,你可以理解为对环境的校验 boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); //初始化一个任务 final Thread t = w.thread; if (t != null) { //当前线程不为空的情况下 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); //获取锁 try { int rs = runStateOf(ctl.get()); //需要重新获取到线程的运行状态,在获取锁之前,他的状态可能被别的线程改变。 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //在线程池是运行状态,或者线程池被关闭了并且第一个线程是空的情况下 if (t.isAlive()) // 如果线程是活跃状态,那么说明先状态有问题,直接抛出异常 throw new IllegalThreadStateException(); workers.add(w); //将任务加到工作线程集里面 int s = workers.size(); if (s > largestPoolSize) //largestPoolSize是记录线程池中出现的最大的线程的数量 largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); //解锁 } if (workerAdded) { t.start(); //线程启动,相应的调用run方法 workerStarted = true; } } } finally { if (! workerStarted) //如果工作线程启动失败,就将它从工作线程集中移除 addWorkerFailed(w); } return workerStarted; }
总结一下,先校验线程的状态,没有问题就创建一个工作线程,如果线程创建没有问题就启动了,如果有问题就将这个工作线程从工作线程集中移除。
3.运行线程
前面已经讲过了将线程建立起来,现在需要使线程跑起来了。大家都知道要使线程跑起来,肯定要执行start方法。没错,线程池里面也是这样干的。
public void run() { runWorker(this); }
继续往下看,woker里面的run方法调用的是线程池里面的runWorker方法。
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); //取到当前线程 Runnable task = w.firstTask; //拿到第一个工作线程的第一个线程 w.firstTask = null; w.unlock(); // 允许出现多个中断 boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { //不断循环取任务 w.lock(); //加锁,别的线程不能进入 //如果线程线程池被关闭了,线程没没有关闭的话,需要中断 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { 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 { afterExecute(task, thrown); //线程执行之后的处理 } } finally { task = null; //将任务置成null w.completedTasks++; //完成的任务加一 w.unlock(); } } completedAbruptly = false; //是否完全结束,false表示 完全结束了 } finally { processWorkerExit(w, completedAbruptly); //清除任务 } }
总结:不断的取任务执行,如果执行完成了记录任务的执行数加一,将这个线程从工作任务(工作线程)集中删除。
进一步查看是怎么获取任务的getTask()
private Runnable getTask() { boolean timedOut = false; for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 如果线程池关了,返回的任务时null if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // 是否允许核心线程数超时,当满足allowCoreThreadTimeOut被设置为true 或当前线程数大于核心线程数时,timed=true boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) //超过最大线程数或超时或任务队列为空 && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) //如果线程数减到了-1 ,就返回null return null; continue; //跳过下面的任务执行 } try { Runnable r = timed ? //设置了超时,按超时的,没有超时的话,就直接从队列的拿出任务来执行 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
到此差不多已经基本上讲完了线程池的运行流程,还有一个添加进工作队列的方法没有讲,涉及到了LinkedBlockQueue,已经专门写了一篇文章来讲解。
其实讲了这么多,大家只要知道线程池的使用和运行流程就可以了。它的使用demo在此就不在讲解了,网上也有很多例子。再来温习一下它的运行流程。
第一步,判断线程是不是为空,如果是空的话,就抛出空指针异常。
第二步,如果线程的数量小于核心线程池的个数,创建新的线程执行任务。
第三步,如果线程的数量大于核心线程的数量,就将线程放到缓存队列中。
第四步,缓存队列的满了,还没有到达最大核心线程池的大小,这个时候就也是创建线程执行任务,如果大于最大的核心线程池的大小,那么就拒绝任务的执行。这里所说执行任务的线程就是Worker.
当然如果设置了超时,在时间范围内,任务肯定是可以被执行的,超时返回的就是一个空的任务,不会执行之前的任务。有一个地方需要注意的是:只有在线程数大于核心线程数的时候,任务才会被放到任务队列里面,小于的话,任务时直接执行的。