概述
一、线程池的介绍
1.线程池的重要性
(1)如果不使用线程池,那么每一个任务都会新开一个线程
- 如果任务很多,那么就会反复创建和销毁很多线程,造成很大的开销。
- 过多的线程会占用太多内存。
(2)线程池的好处
- 加快响应速度
- 合理利用CPU和内存
- 统一管理
(3)线程池适合应用的场合
- 服务器:会收到大量请求
- 实际开发中,需要创建5个以上的线程时,就可以使用线程池。
二、创建和停止线程池
1.线程池构造函数的参数
- corePoolSize 指的是核心线程数:线程池完成初始化后,默认情况下没有任何线程。等到有任务到来时,线程池才会创建线程去执行任务。核心线程会一直存活。
- maxPoolSize 线程数的上限。
2.添加线程规则
- 如果线程数小于corePoolSize,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。
- 线程数大于等于corePoolSize但少于maxPoolSize,则将任务放入队列。
- 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。
- 如果队列已满,并且线程数大于等于maxPoolSize,则拒绝该任务。
3.增减线程的特点
- 当corePoolSize等于maxPoolSize,就可以创建固定大小的线程池。
- 线程池希望保持较少的线程数,只有在负载变得很大时才增加它。
- maxPoolSize设置为很高的值,便可以允许线程池容纳任意数量的并发任务。
- 如果使用无界队列,那么线程数就不会超过corePoolSize。
4.keepAliveTime
如果线程数多于corePoolSize,那么多余的线程空闲时间超过keepAliveTime就会被终止。
5.workQueue
三种常见队列类型:
- 直接交接:SynchronousQueue,没有队列作为缓冲
- 无界队列:LinkedBlockingQueue
- 有界队列:ArrayBlockingQueue
6.停止线程池的正确方法
- shutdown(),线程池会在完成池中的任务后关闭,这期间提交新任务会被拒绝
- isShutdown(),可以判断是不是进入停止状态了
- isTeminated(),会判断线程池是否完全停止了
- awaitTermination(),等待一段时间,如果在此期间线程池停止了就返回true,否则返回false。
- shutdownNow(),立刻停止线程池。会给所有正在执行的线程发送interrupt信号,剩下的线程会返回。
三、手动创建还是自动创建
手动创建更好,可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。
自动创建的线程池:
1.FixedThreadPool
FixedThreadPool在创建时指定大小,并且这个线程池永远都是这么大。
使用如下
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
for(int i=0;i<1000;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
通过观察源码,可以发现,其核心线程数与最大线程数相等,并且使用无界队列。这样当请求堆积时,容易造成占用大量的内存,可能会导致OOM。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.SingleThreadExecutor
整个线程池中只有一个线程,原理和FixedThreadPool相同。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
3.CachedThreadPool
通过观察源码,可以发现其核心线程数为0,最大线程数无穷大,并且使用直接交接。也就是说只要有任务,而且此时没有空闲的线程,就会新建一个线程去执行,当线程空闲60s,便会回收。
创建的线程过多也会导致OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4.ScheduledThreadPool
可以用来周期性的执行任务。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
采用的是DelayWorkQueue作为工作队列,延迟队列DelayQueue是一个无界阻塞队列,它的队列元素只能在该元素的延迟已经结束或者说过期才能被出队。
5.线程数量设定为多少比较合适
6.workStealingPool
workStealingPool是JDK1.8加入的。
这个线程池和之前的有很大不同,里面的任务是可以产生子任务的(比如树的遍历)。每个线程之间可以合作,线程有自己的任务队列,当一个线程执行完后就可以去窃取其他线程队列末端的任务去执行。
四、任务太多,怎么拒绝
1.拒绝时机
- 当Executor关闭时,提交新任务会被拒绝
- 当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时。
2.拒绝策略
- AbortPolicy 抛出异常
- DiscardPolicy 默默丢弃任务
- DiscardOldestPolicy 丢弃队列中最老的任务
- CallerRunsPolicy 让提交任务的线程去执行这个任务
五、钩子方法
在任务执行的前后可以添加方法,可以用来做日志、统计以及别的。
这里我们设置一个标志位isPaused,在每次任务执行前都会判断这个标志位,如果被设置为true便暂停线程池。
这样便实现了一个可以暂停的线程池。
//自己写一个可暂停的线程池,每个任务前后都可以写钩子函数
public class PauseThreadPool extends ThreadPoolExecutor {
private boolean isPaused;
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
public PauseThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
lock.lock();
try {
while (isPaused){
unpaused.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
private void pause(){
lock.lock();
try {
isPaused = true;
}finally {
lock.unlock();
}
}
private void resume(){
lock.lock();
try{
isPaused = false;
unpaused.signalAll();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
PauseThreadPool pauseThreadPool = new PauseThreadPool(10,20,10L,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<10000;i++){
pauseThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("我被执行了");
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
}
Thread.sleep(1500);
pauseThreadPool.pause();
System.out.println("线程池被暂停了");
Thread.sleep(1500);
System.out.println("恢复线程池");
pauseThreadPool.resume();
}
}
六、实现原理、源码分析★
1.线程池组成部分
- 线程池管理器
- 工作线程
- 任务队列
- 任务接口
2.Executor家族
Executors是一个工具类,可以直接创建线程池。
3.线程池实现线程复用的原理
final void runWorker(Worker w) {
//wt为当前的线程
Thread wt = Thread.currentThread();
//获取传入的任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//在其中调用任务的run方法。
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
七、线程池状态
- Running:接受新任务并处理排队任务
- Shutdown:不接受新任务,但处理排队任务
- Stop:不接受新任务,也不处理排队任务,并且中断正在进行的任务。
- Tidying:所有任务都已经终止,workerCount为零时,线程会转换到Tidying状态,并将运行terminate()钩子方法。、
- Terminated:terminate()运行完成。
八、使用线程池注意点
- 避免任务堆积
- 避免线程数过度增加
- 排查线程泄露
最后
以上就是大力西牛为你收集整理的线程池【治理线程的最大法宝】的全部内容,希望文章能够帮你解决线程池【治理线程的最大法宝】所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复