茫茫人海千千万万,感谢这一秒你看到这里。希望我的面试题系列能对你的有所帮助!共勉!
愿你在未来的日子,保持热爱,奔赴山海!
昨天既然聊到线程池中的实现方式,有些比较重要的我还没问到。
我们先来看看它的构造方法有哪些:
// 五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {...}
// 六个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {...}
// 六个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {...}
// 七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
我们再来详解下构造方法中涉及的7个参数,其中最重要5个参数就是第一个构造方法中的。
int corePoolSize:该线程池中核心线程数量
核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干,而非核心线程(临时工)如果长时间的闲置,就会被销毁。但是如果将
allowCoreThreadTimeOut
设置为true时,核心线程也是会被超时回收。
int maximumPoolSize:该线程池中允许存在的工作线程的最大数量。
该值相当于核心线程数量 + 非核心线程数量。
long keepAliveTime:非核心线程闲置超时时长。
非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true)
,则会也作用于核心线程。
TimeUnit unit:keepAliveTime的时间单位。
TimeUnit是一个枚举类型 ,包括以下属性:
NANOSECONDS : 1微毫秒
MICROSECONDS : 1微秒
MILLISECONDS : 1毫秒
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
BlockingQueue workQueue:阻塞队列,维护着等待执行的Runnable任务对象。
当新任务来的时候,会先判断当前运行线程数量是否达到了核心线程数,如果达到了,就会被存放在阻塞队列中排队等待执行。
常用的几个阻塞队列:
ArrayBlockingQueue
数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
SynchronousQueue
同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
DelayQueue
延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。
LinkedBlockingQueue
链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE
,也可以指定大小。
还有两个非必须的参数:
ThreadFactory threadFactory
创建线程的工厂 ,用于批量创建线程,统一在创建线程时设置一些参数,如是否守护线程、线程的优先级等。如果不指定,会新建一个默认的线程工厂。
RejectedExecutionHandler handler
拒绝处理策略,在线程数量大于最大线程数后就会采用拒绝处理策略,四种拒绝处理的策略为 :
RejectedExecutionException
异常。不错呀!线程池的参数也有深入了解,那咱们继续
昨天MyGirl跟我讲了一下她去银行办理业务的一个场景:
而实际上线程的流程原理跟这个一样,我们来看下处理任务的核心方法execute
,它的源码大概是什么样子的呢,当然我们也可以看源码中的注释,里面也写的很清楚。这里具体讲下思路。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 1. 获取ctl,ctl是记录着线程池状态和线程数。
int c = ctl.get();
// 2. 判断当前线程数小于corePoolSize核心线程,则调用addWorker创建核心线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 创建线程失败,需要重新获取clt的状态和线程数。
c = ctl.get();
}
// 3. 如果不小于corePoolSize,进入下面的方法。
// 判断线程池是否运行状态并且运行线程数大于corePoolSize,将任务添加到workQueue队列。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 3.1 再次检查线程池是否运行状态。
// 如果isRunning返回false(状态检查),则remove这个任务,然后执行拒绝策略。
if (! isRunning(recheck) && remove(command))
reject(command);
// 3.2 线程池处于running状态,但是没有线程,则创建线程加入到线程池中
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 4. 如果放入workQueue失败,则创建非核心线程执行任务,
// 如果这时创建非核心线程失败(当前线程总数不小于maximumPoolSize时),就会执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}
我们可以大概看下思路图:
先解释下ctl
变量ctl定义为AtomicInteger,记录了“线程池中的任务数量”和“线程池的状态”两个信息。以高三位记录着线程池的状态和低29位记录线程池中的任务数量。
RUNNING : 111
SHUTDOWN : 000
STOP : 001
TIDYING : 010
TERMINATED : 011
最后总结一下执行过程:
mainLock.lock();
。不错,这个执行过程原理都有深入了解过,最后问你一道:
你这怕不是魔鬼吧,写一个线程池。不过简单的线程池还是可以写写滴!当然通过上面参数,执行过程的学习,写出来一个还是比较So Easy的。只是如果真的到面试了,真的让你手敲,可能就忘了,还是得多敲。
这里还是直接用简单的ThreadPoolExecutor创建吧,等后续写线程池相关文章,再详细写自己创建的线程池吧。
我们先创建一个任务类Task:
/**
* 自定义任务类
*/
public class Task implements Runnable{
private int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "即将执行的任务是" + id + "任务");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完成的任务是" + id + "任务");
}
}
测试代码:
public class ThreadPoolExecutorDemo {
private static final int CORE_POOL_SIZE = 3;
private static final int MAX_POOL_SIZE = 5;
private static final int QUEUE_CAPACITY = 10;
private static final Long KEEP_ALIVE_TIME = 1l;
public static void main(String[] args) {
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
Task task = new Task( i);
//执行Runnable
executor.execute(task);
}
//终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("线程已经全部执行完");
}
}
得到的结果:
pool-1-thread-1即将执行的任务是0任务
pool-1-thread-3即将执行的任务是2任务
pool-1-thread-2即将执行的任务是1任务
pool-1-thread-1执行完成的任务是0任务
pool-1-thread-3执行完成的任务是2任务
pool-1-thread-1即将执行的任务是3任务
pool-1-thread-3即将执行的任务是4任务
pool-1-thread-2执行完成的任务是1任务
pool-1-thread-2即将执行的任务是5任务
pool-1-thread-3执行完成的任务是4任务
pool-1-thread-1执行完成的任务是3任务
pool-1-thread-3即将执行的任务是6任务
pool-1-thread-1即将执行的任务是7任务
pool-1-thread-2执行完成的任务是5任务
pool-1-thread-2即将执行的任务是8任务
pool-1-thread-3执行完成的任务是6任务
pool-1-thread-1执行完成的任务是7任务
pool-1-thread-3即将执行的任务是9任务
pool-1-thread-2执行完成的任务是8任务
pool-1-thread-3执行完成的任务是9任务
线程已经全部执行完
当然此版写的稍微简单,但是如果真的忘记,也可以这么写。如果后续想看更多东西,可以关注我呀,我会持续更新内容!
小伙子不错嘛!今天就到这里,期待你明天的到来,希望能让我继续保持惊喜!
参考资料:线程池原理
注: 如果文章有任何错误和建议,请各位大佬尽情留言!如果这篇文章对你也有所帮助,希望可爱亲切的您给个三连关注下,非常感谢啦!