本文分析下线程的工作流程
- 线程池创建。
- 执行任务过程。
- 创建线程过程。
- 线程池中的线程执行任务过程。
- 回收线程过程。
- 任务缓冲队列。
因为在工作中遇到了一些线程池的使用问题,百度了许久,都未找到答案,所以就自己去看了下源码。特此记录下。
使用线程池遇到的问题
- 核心线程已用完,不扩建线程。(最大线程数 > 核心线程数)的情况。
之前的错误理解
- 核心线程都在工作,再添加任务,无论任务队列是否满了,只要工作线程数小于最大线程数,都会创建线程。
- 任务放置队列中,是工作线程数达到最大线程数。
- 核心线程不会被回收。
- 有独立线程在回收线程池中的空闲线程。
线程池工作流程和常用API及使用demo
- 1.0 创建线程池
- 2.0 执行任务过程
- 3.0 创建线程过程
- 4.0 线程池中的线程执行任务过程
- 5.0 回收线程过程
- 6.0 任务缓冲队列
- 7.0 线程池中的线程执行任务总体工作流程
- 8.0 线程池其他常用API
- 9.0 线程池演示Demo
1.0 创建线程池
创建线程池的构造参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, // 核心线程数,长期保持活跃数量, 不会被回收
10, // 最大线程数, 包括(核心线程数)
0L, // 空闲线程最大允许多长时间被回收
TimeUnit.MILLISECONDS, // keepAliveTime 的时间单位
new LinkedBlockingQueue<>(2), // 线程池执行任务队列
Executors.defaultThreadFactory(), // 创建线程的工厂对象
new ThreadPoolExecutor.AbortPolicy() // 执行任务失败处理器
);
1.1 threadFactory
ThreadFactory接口,其中只有一个 newThread()方法,用于创建线程
默认值:Executors.defaultThreadFactory()
代码如下:
- 设置保护线程为false。
- 设置线程名:pool-poolNumber-thread-threadNumber
- 设置优先级
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
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;
}
1.3 handler
RejectedExecutionHandler接口,其中只有一个 rejectedExecution()方法,用于失败执行。
默认值:new ThreadPoolExecutor.AbortPolicy()
代码如下:
失败抛出异常:RejectedExecutionException
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
还可参考其他选项:
- ThreadPoolExecutor.CallerRunsPolicy
- ThreadPoolExecutor.DiscardPolicy
- ThreadPoolExecutor.DiscardOldestPolicy
2.0 执行任务过程
线程池执行任务:executor.execute(Runnable)
execute 流程
3.0 创建线程过程
创建线程方法:addWorker(Runnable firstTask, boolean core),core参数表示:是否在小于核心线程数的情况下创建线程。
4.0 线程池中的线程执行任务过程
线程轮询在队列中获取任务。
5.0 回收线程过程
从任务队列中获取一个任务超时,正常结束。
6.0 任务缓冲队列
核心方法:
- boolean offer(E)
- E poll(timeout, unit) 获取并移除队列头节点。如果队列为空,则等待${timeout}时间,如果还是没有数据,则返回null。
- E take()
7.0 线程池中的线程执行任务总体工作流程
结合以上知识:参考如下图
大致流程描述:
- 执行任务,核心线程数未达到corePoolSize该值,就创建线程。否则塞入任务队列。
- 任务队列满了,就创建线程,但是总线程数不能超过该值maximumPoolSize。
- 如果任务队列满了,线程数达到maximumPoolSize值,则执行失败策略。
- 工作线程则不停的轮询去队列中poll任务,如果poll为空,则工作线程执行结束(回收线程)。
- 如果工作线程数<=核心线程数corePoolSize,则使用take从队列中获取任务(核心线程一直await)。
8.0 线程池其他常用API
- prestartCoreThread:预先启动一个核心线程。
- prestartAllCoreThreads:预先启动全部核心线程。
- allowCoreThreadTimeOut:设置核心线程是否可以被回收。
- getActiveCount:获取当前正在执行任务的线程的大致数量。
- getLargestPoolSize:获取线程池有史以来最大的线程数。
- getPoolSize:获取当前线程池数量。
- getCompletedTaskCount:获取已完成执行的任务的大致总数。
- getQueue:获取任务队列。
- remove:从任务队列中移除一个任务。
- shutdown:将队列中的任务执行完毕后,停止线程池执行器。但是不接受新的任务。
- shutdownNow:尝试停止所有正在执行的任务,暂停正在等待的任务的处理,并返回正在等待执行的任务列表。从该方法返回后,这些任务将从任务队列中排出(删除)。
- execute:执行一个任务。
- submit:执行一个任务,可接受Callable类型。
9.0 线程池演示Demo
演示代码如下:
核心线程数2个,最大线程数10个,队列容量20个。
执行 22 次任务。任务里面休眠(1000 * 1000)毫秒。
执行结果:2个核心线程创建,执行两次任务,进入休眠。剩余20次任务都塞入队列。
并不会去创建线程
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public void test() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
10, // 最大线程数
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(20) // 任务队列容量
);
// 执行 22 次任务
for (int i = 0; i < 22; i++) {
executor.execute(new DemoRunnable(i));
}
}
public static class DemoRunnable implements Runnable {
final int value;
static long time = 1000L * 1000L; // 休眠长一点
public DemoRunnable(int value) {
this.value = value;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " - value = " + value);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new ThreadPoolDemo().test();
}
}
执行效果截图:
将执行任务次设置为23次,队列满了,再执行任务,则会创建线程,进行执行当前任务。