面试必问高频
面试官:了解线程池嘛?
我:了解
面试官:为啥子采用线程池?有啥子优点?
我:我大概分为3点
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
面试官:都了解哪些线程池?
我:我暂时知道的一些的如:newFixedThreadPool(固定线程池)、newSingleThreadExecutor(单个线程的线程池)、newCachedThreadPool(缓存线程的线程池)、newScheduledThreadPool(带定时器的线程池),还有几个就不说了。
我就举点源码吧
newFixedThreadPool:
// core和max是一样的
// blockQueue是无界阻塞队列
// 嗯, 不好不好!!!
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
// core和max无非都是1而已
// blockQueue是无界阻塞队列
// 嗯, 不好不好!!!
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
// core 0
// max有点狠,不怕暴栈?
// 队列还是SynchronousQueue,还真怕
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
面试官:讲一下线程池的参数?
我:没问题,ThreadPoolExecutor源码走起:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数线程数定义了最小可以同时运行的线程数量
- maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
- keepAliveTime:当线程数大于核心线程数时,多余的空闲线程存活的最长时间
- TimeUnit:时间单位
- BlockingQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中
- ThreadFactory:线程工厂,用来创建线程,一般默认即可
- RejectedExecutionHandler:拒绝策略
面试官:讲一下都有哪些拒绝策略
我:还好我提前准备
- AbortPolicy:抛出
RejectedExecutionException
来拒绝新任务的处理。 - CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。(说白了,谁管理任务的,谁就负责帮忙)
- DiscardPolicy:不处理新任务,直接丢弃掉。
- DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。
面试官:线程池的线程数量怎么确定
我:分情况,一般来说...
- 一般来说,如果是CPU密集型应用,则线程池大小设置为N+1。
- 一般来说,如果是IO密集型应用,则线程池大小设置为2N+1。
- 在IO优化中,线程等待时间所占比例越高,需要越多线程,线程CPU时间所占比例越高,需要越少线程。这样的估算公式可能更适合:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
面试官:shutdown和shutdownNow的区别
我:上源码:
// 等待所有线程执行任务完毕之后退出
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 获取锁
mainLock.lock();
try {
// 检查
checkShutdownAccess();
// 设置状态
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 主要在于这里,根据状态来是否立马停止还是等线程执行完毕过后停止
tryTerminate(); // 这里就不贴了
}
// 和上面的差不多,立马中断所有线程,关闭线程池
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置状态
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
面试官:execute和submit的区别
我:心累
// execute
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.
*/
// 按还是翻译了一下:
// 1.如果正在运行的线程少于corePoolSize线程,请尝试使用给定命令作为其第一个任务来启动新线程。
// 对addWorker的调用从原子上检查runState和workerCount,从而通过返回false来防止在不应该添加线程的情况下发出虚假警报。
// 2.如果一个任务可以成功排队,那么我们仍然需要仔细检查是否应该添加一个线程(因为现有线程自上次检查后就死掉了)或该池自进入该方法后就关闭了。
// 因此,我们重新检查状态,并在必要时回滚排队(如果已停止),或者在没有线程时启动一个新线程。
// 3.如果我们无法将任务排队,则尝试添加一个新线程。
// 如果失败,我们知道我们已关闭或已饱和,因此拒绝该任务。
// 总结:说白了,就是上面的流程图
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);
}
//不过传递的参数,Runnable,那么就意味着没有返回值
// 简单看一下submit吧。
// 不必多说了
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);