Java线程池
什么是线程池?
线程池:从它名字上来看,就是把一堆线程放进一个池子里面,来任务后取出池子里面的线程,工作完后放回池子。
为什么要使用这个?
首先要明白线程分为用户级线程和内核级别线程。我们使用new Thread()创建的线程其实底层是通过系统调用创建的内核级别线程。既然是内核级别线程,那么必然会创建一个TCB,TCB是消耗内存的。
所以
好处1:能够避免频繁的系统调用。系统调用对于任何一个操作系统开销都是很大的。
好处2:消耗内存,熟悉JVM的都知道,虚拟机栈、程序计算器是随着线程的生命周期的,自然而然,当并发量高的时候,这些东西也会创建很多,栈默认是1m,可以通过-xss参数来改变大小。
用法:
自定义线程池构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handle) {};
一共有7个参数:
corePoolSize:核心线程数,这些线程是一直存在线程池中的
maximumPoolSize:最大线程数
keepAliveTime:存活时间,当超过核心线程线程后,但是小于最大线程数,这些线程如果没有任务,会在keepAliveTime时间后消亡。
unit:单位,keepAliveTime的单位。
workQueue:阻塞队列,这里规定了阻塞队列里面放的元素必须是Runnable,当核心线程都在工作时,这时候新来的任务就会被放进阻塞队列。
threadFactory:线程工厂。
我们看看threadFactory是啥,其实里面很简单,就是接口,里面有个方法来返回创建的后的线程,简单点说:线程池里面的线程都是通过下面的方法newThread()来创建的
public interface ThreadFactory { /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ Thread newThread(Runnable r); }
handler:拒绝策略。简单点说就是当你最大线程数的线程都在工作时,并且阻塞队列也满了,这个时候你怎么去处理新的任务。jdk提供了四种拒绝策略:
ThreadPoolExecutor.CallerRunsPolicy(): 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy() :抛弃当前的任务
ThreadPoolExecutor.AbortPolicy() :抛出java.util.concurrent.RejectedExecutionException异常, Default异常
ThreadPoolExecutor.CallerRunsPolicy() :由创建了线程池的线程来执行被拒绝的任务
也可以自定义自己的拒绝策略,实现下面的接口就行
public interface RejectedExecutionHandler { /** * Method that may be invoked by a {@link ThreadPoolExecutor} when * {@link ThreadPoolExecutor#execute execute} cannot accept a * task. This may occur when no more threads or queue slots are * available because their bounds would be exceeded, or upon * shutdown of the Executor. * * <p>In the absence of other alternatives, the method may throw * an unchecked {@link RejectedExecutionException}, which will be * propagated to the caller of {@code execute}. * * @param r the runnable task requested to be executed * @param executor the executor attempting to execute this task * @throws RejectedExecutionException if there is no remedy */ void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
原理:
构造方法很简单,就不具体展开了
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; }
关键方法:
注意:ctl 可以理解是两个字段的打包,一个是线程池的状态,后面一个是workerCount,可以看到初始化时,是RUNNING和0;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); public void execute(Runnable command) { //1.参数校验 if (command == null) throw new NullPointerException(); //获取ctl的值 int c = ctl.get(); //通过workerCountOf(c)反过来获取workerCount,如果小于核心线程数 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //如果大于核心线程数,并且能够正常加入阻塞队列,注意这里用的是offer,不是add if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); //线程池关闭了 if (! isRunning(recheck) && remove(command)) reject(command); //wc == 0,不知道这种是什么情况,暂时不用管 else if (workerCountOf(recheck) == 0) addWorker(null, false); } //如果满了,尝试加入最大线程,可以看到后面参数为false,这里就表示为最大线程 else if (!addWorker(command, false)) //加入失败,执行拒绝策略。里面代码很简单 reject(command); }
//这里core表示新加的线程是否是核心线程 private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); //通过runStateOf函数返过来获取线程池状态 int rs = runStateOf(c); // 校验 if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); //校验当前workerCount if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //通过CAS则增加ctl的值,增加失败,就重试,这里解决多线程下并发增加ctl问题 if (compareAndIncrementWorkerCount(c)) break retry; //增加失败重新获取ctl值 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 { //创建Worker w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); //又是线程池状态校验 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //增加这个worker workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { //增加成功后,开启线程 t.start(); workerStarted = true; } } } finally { if (! workerStarted) //如果开启失败,加入失败 addWorkerFailed(w); } return workerStarted; }
看看new Worker(),可以就看到创建了一个worker,一个worker里面有一个线程,这个线程就是我们的线程池构造方法里面的线程工厂创建的,但是的Runnable是这个Worker
因为Worker类是实现了了Runnable的,这里有点绕
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
}
当线程调用start方法后,cpu会自动调用run()方法。也就是worker里面的run方法
public void run() { runWorker(this); } final void runWorker(Worker w) { //获取当前线程 Thread wt = Thread.currentThread(); //获取任务 Runnable task = w.firstTask; //将这个worker里面任务置为空, w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //当task不为空 while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //执行任务前要执行的方法,可以扩展,默认为空方法 beforeExecute(wt, task); Throwable thrown = null; try { //直接调用run方法 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; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { //执行完了,移除worker,其实这里正常情况下只会移除非核心线程 processWorkerExit(w, completedAbruptly); } }
从阻塞队列里面获取任务
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? //死循环 for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? //可以看到后面wc>corePoolSize,就是大于核心线程数 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //这里回收worker, if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { //CAS减少workerCount数量 if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? //多少时候就不等了,这里keepAliveTime其实就是我们传进来的参数,相当于如果大于核心线程数,如果keepAliveTime时候后,仍然取不到,会减少workerCount的数量 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : //阻塞,等待队列里面有任务,核心线程就是,所以核心线程不会退出 workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
总结与思考:
到这里也就大致明白了几个点:
第一:核心线程为什么不会消亡?
因为核心线程从阻塞队列里面取任务时,采用的take(),它是阻塞的。
第二:非核心线程为什么会消亡?
其实核心线程和非核心线程是一样的,只是在获取任务时,会判断当前wc(workerCount)数量,如果大于核心线程,就会执行CAS减少worker数量,通过CAS来确保多线程下workerCount数量的正确性,然后移除这个worker。
第三:一个线程为什么能够执行多个任务?
其实线程还是只能执行一个任务,只不过这个任务是不断的从阻塞队列里面取出任务,调用它们的run()方法进行执行。
第四:如果线程次第一个线程执行完了,summit第二个任务时,会新开启一个线程吗?
会,从源码角度上看,只要线程池数量小于核心线程数,都是先加入线程,当大于核心线程数后,才会进入阻塞队列,从阻塞队列里面取。