系列文章目录
Java自学日记之多线程(一):线程与进程以及线程的创建
Java自学日记之多线程(二):线程安全以及一些简单的线程同步方法
Java自学日记之多线程(三):阻塞队列
文章目录
一、线程池的执行流程
线程池执行所提交的任务过程:
- 先判断线程池中核心线程池所有的线程是否都在执行任务。 如果不是,则新创建一个线程执行刚提交的任务,否则,核心线程池中所有的线程都在执行任务,则进入第2步;
- 判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞队列中;否则,则进入第3步;
- 判断线程池中所有的线程是否都在执行任务,如果没有,则创建一个新的线程来执行任务,否则,则交给饱和策略进行处理
二、线程池的周期
1.线程池的五种状态
在java.util.concurrent.ThreadPoolExecutor类中,我们能看到线程池被总结出五种状态
// runState is stored in the high-order bits
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;
private static final int TERMINATED = 3 << COUNT_BITS;
注释中解释了这五种状态
- RUNNING: Accept new tasks and process queued tasks (接受新任务并处理排队的任务)
- SHUTDOWN: Don’t accept new tasks, but process queued tasks
(不接受新任务,但可以处理排队的任务) - STOP: Don’t accept new tasks, don’t process queued tasks, and
interrupt in-progress tasks (不接受新任务、不处理排队的任务并且中断正在进行的任务) - TIDYING: All tasks have terminated, workerCount is zero, the thread
transitioning to state TIDYING will run the terminated() hook method
(当所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated()方法进入TERMINATED 状态。) - TERMINATED: terminated() has completed The numerical order among
these values matters, to allow ordered comparisons. (在terminated()
方法执行完后进入该状态,表示线程池彻底终止(这串英文注释没读懂,在网上找了个解释。。))
The runState monotonically increases over time, but need not hit each state.
运行状态会随着时间单调增加,但是不需要到达每一个状态
2.线程池各种状态之间的转换
- RUNNING -> SHUTDOWN
On invocation of shutdown()
调用shutdown()函数 - (RUNNING or SHUTDOWN) -> STOP
On invocation of shutdownNow()
调用shutdownNow()函数 - SHUTDOWN -> TIDYING
When both queue and pool are empty
当队列和线程池都为空 - STOP -> TIDYING
When pool is empty
当线程池为空(因为此时队列中的任务已经被中断了) - TIDYING -> TERMINATED
When the terminated() hook method has completed Threads waiting in awaitTermination() will return when the state reaches TERMINATED.
当terminated()hook方法完成时,等待终止的线程将在状态达到terminated时返回
三、几种常见的线程池
1.ThreadPoolExecutor
(1).newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程
特征:
- 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
- 线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
- 当线程池中,没有可用线程,会重新创建一个线程
(2).newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
特征:
- 线程池中的线程处于一定的量,可以很好的控制线程的并发量
- 线程可以重复被使用,在显示关闭之前,都将一直存在
- 超出一定量的线程被提交时候需在队列中等待
(3).newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特征:
- 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
2.ScheduledThreadPoolExecutor
(1).newSingleThreadScheduledExecutor
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
特征:
- 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
- 可定时或者延迟执行线程活动
(2).newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
特征:
- 线程池中具有指定数量的线程,即便是空线程也将保留
- 可定时或者延迟执行线程活动
四、线程池的拒绝策略
线程池的拒绝策略有四种
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛 出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的 任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
五、execute方法
当我们需要向线程池中放入线程时,需要用到execute或submit方法,这里介绍一下excute
excute源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
源码里的注释已经很详细了,详细运行规则可以直接看注释
总结一下处理逻辑就是
- 如果当前运行的线程少于corePoolSize,则会创建新的线程来执行新的任务;
- 如果运行的线程个数等于或者大于corePoolSize,则会将提交的 任务存放到阻塞队列workQueue中;
- 如果当前workQueue队列已满的话,则会创建新的线程来执行 任务;
- 如果线程个数已经超过了maximumPoolSize,则会使用饱和策 略RejectedExecutionHandler来进行处理。
总结
因为今天脑袋有点疼,所以总结的有点草,只想早点写完睡觉了= =
多线程目前就先看到这里吧,更深的知识点等我全学完再回来看吧