本文共 3400 字,大约阅读时间需要 11 分钟。
上次我们简单了解了一下什么是线程池以及Java中几种类型的线程池,今天我们来深入剖析一下线程池的原理。
1. 线程池管理器(ThreadPoolManager):用于创建并管理线程池
2. 工作线程(WorkThread): 线程池中线程
3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
注:线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
a.一个任务提交,如果线程池大小没达到corePoolSize,则每次都启动一个worker也就是一个线程来立即执行
b.如果来不及执行,则把多余的线程放到workQueue,等待已启动的worker来循环执行 c.如果队列workQueue都放满了还没有执行,则在maximumPoolSize下面启动新的worker来循环执行workQueue d.如果启动到maximumPoolSize还有任务进来,线程池已达到满负载,此时就执行任务拒绝RejectedExecutionHandler
// 流程就是:没达到corePoolSize,创建worker执行,达到corePoolSize加入workQueue// workQueue满了且在maximumPoolSize下,创建新worker,达到maximumPoolSize,执行rejectpublic void execute(Runnable command) { if (command == null) throw new NullPointerException(); // 1:poolSize达到corePoolSize,执行3把任务加入workQueue // 2:poolSize没达到,执行addIfUnderCorePoolSize()在corePoolSize内创建新worker立即执行任务 // 如果达到corePoolSize,则同上执行3 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { // 3:workQueue满了,执行5 if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) { // 4:如果线程池关闭,执行拒绝策略 // 如果poolSize==0,新启动一个线程执行队列内任务 ensureQueuedTaskHandled(command); } // 5:在maximumPoolSize内创建新worker立即执行任务 // 如果达到maximumPoolSize,执行6拒绝策略 } else if (!addIfUnderMaximumPoolSize(command)) // 6:拒绝策略 reject(command); // is shutdown or saturated }}
keepAliveTime:代表的就是线程空闲后多久后销毁,线程的销毁是通过worker的getTask()来实现的。
一般来说,Worker会循环获取getTask(),如果getTask()返回null则工作线程worker终结,那我们再看看什么时候getTask()返回null
Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) return null; Runnable r; if (state == SHUTDOWN) // Help drain queue r = workQueue.poll(); else if (poolSize > corePoolSize || allowCoreThreadTimeOut) // 在poolSize大于corePoolSize或允许核心线程超时时 // 阻塞超时获取有可能获取到null,此时worker线程销毁 r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); else r = workQueue.take(); if (r != null) return r; // 这里是是否运行worker线程销毁的判断 if (workerCanExit()) { if (runState >= SHUTDOWN) // STOP或TERMINATED状态,终止空闲worker interruptIdleWorkers(); return null; // 这里返回null,代表工作线程worker销毁 } // 其他:retry,继续循环 } catch (InterruptedException ie) { // On interruption, re-check runState } }}
平缓关闭 shutdown:这个方法会将runState置为SHUTDOWN,会终止所有空闲的线程,同时不再接受新的任务,而仍在工作的线程不受影响,所以队列中的任务人会被执行
立即关闭 shutdownNow:此方法将runState置为STOP,和shutdown方法的区别是,这个方法会终止所有的线程(取消所有正在执行和未执行的任务),所以队列中的任务也不会被执行了。
通过这次学习,我们不仅知道了线程池是如何管理线程,而且还了解了线程多种状态之间的转换,这样更加便于我们对线程池的理解。之后,我们还将继续学习关于多线程的知识,让我们每天都有成长。