线程池的基本使用,以及常见问题
想线程池提交任务,执行流程
线 | 程 | 池 |
---|---|---|
提交任务 | ||
↓ | ||
执行任务 | ←否 | 核心线程池是否已满 |
↓是 | ||
将任务放入缓存队列 | ←否 | 等待队列是否已满 |
↓是 | ||
创建线程执行任务 | ←否 | 线程池是否达到最大线程 |
↓是 | ||
执行拒绝策略处理无法处理的任务 |
使用线程池三种方式
ExecutorService threadPool = new Executors.newFixedThreadPool(5)
//一池固定数量线程
ExecutorService threadPool = new Executors.newSingleThreadPool();
//一池一线线程
ExecutorService threadPool = new Executors.newCachedThreadPool();
//一池n线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"处理任务");
});
七大参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
timeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize
线程池中的窗柱核心线程数 - maximumPoolSize
线程池能够容纳同时执行的最大线程数,此值必须大于等于一; - keepAliveTime
多余的空闲线程的存活时间。(当线程池线程数量超过corePoolSize时,空闲时间达到keepAliveTime时会被销毁直到剩下corePoolSize个线程为止) - unit
keepAliveTime的单位 - workQueue
任务队列,被提交但尚未执行的任务 - threadFactory
表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可 - handler
拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)
拒绝策略
-
AbortPolicy(默认)
直接抛出RejectedExecutionException异常,阻止系统正常运行 -
CallerRunsPolicy
“调用者运行” 一种调节机制,不抛异常也不抛弃任务,而是将某任务回退给调用者。 -
DiscardOldestPolicy
抛弃队列中等待最久的任务,将新任务加入队列,尝试再次提交 -
DiscardPolicy
直接丢弃任务,不予任何处理(如果允许丢弃,此方案最佳)
如果这几种拒绝策略不能满足需求,我们还可以通过重写RejectedExecutionHandler的rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor)方法
package java.util.concurrent;
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}
面试填坑
前面不是说有三种使用线程池的方式吗。那你平时用哪个比较多?
回答:三选一 ×
回答:哪个都不用,我一般都会使用
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
timeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
自定义线程池的一些参数。
因为上述三种方法,默认阻塞队列虽说是有界的但是,最大值是Integer.MAX不太现实。
所以我们需要手动传入一些参数,来保证业务的正常运行。
在ThreadPoolExecutor中还有三个钩子方法(默认是空方法),我们可以重写该方法来执行一些操作
protected void beforeExecute(Thread var1, Runnable var2) {
}
protected void afterExecute(Runnable var1, Throwable var2) {
}
protected void terminated() {
}
在ThreadPoolExecutor runWorker方法里调用
final void runWorker(ThreadPoolExecutor.Worker var1) {
Thread var2 = Thread.currentThread();
Runnable var3 = var1.firstTask;
var1.firstTask = null;
var1.unlock();
boolean var4 = true;
try {
while(var3 != null || (var3 = this.getTask()) != null) {
var1.lock();
if ((runStateAtLeast(this.ctl.get(), 536870912) || Thread.interrupted() && runStateAtLeast(this.ctl.get(), 536870912)) && !var2.isInterrupted()) {
var2.interrupt();
}
try {
this.beforeExecute(var2, var3);
Object var5 = null;
try {
var3.run();
} catch (RuntimeException var28) {
var5 = var28;
throw var28;
} catch (Error var29) {
var5 = var29;
throw var29;
} catch (Throwable var30) {
var5 = var30;
throw new Error(var30);
} finally {
this.afterExecute(var3, (Throwable)var5);
}
} finally {
var3 = null;
++var1.completedTasks;
var1.unlock();
}
}
var4 = false;
} finally {
this.processWorkerExit(var1, var4);
}
}
合理使用线程池,能够提高性能,方便线程资源的监控与管理。因为频繁创建销毁线程是比较消耗性能的。使用线程池当任务进来的时候不需要等待线程创建就可以直接运行可以提高效率。
但如果线程池使用的不合理,往往会出生反面效果。
这里举一个比较极端的例子来理解。
比如说设置 CoreThread 为 1 阻塞队列的容量为50 MaxThread数 为 10 有5个任务需要线程池执行,但第一个任务里写一个死循环。虽然只有5个任务,但由于CoreThread线程为1 且第一个任务有时死循环。导致其他四个任务放在队列中,等待第一个任务执行完毕,这样无限等待其他4个人任务也不会创建新线程去执行。
自定义创建线程池加优雅的关闭线程池
public class cloud {
private static final int CORE_NUMBER = Runtime.getRuntime().availableProcessors();
public static void main(String[] args) {
//io密集型
System.out.println("cpu core number :"+CORE_NUMBER);
ThreadPoolExecutor instance = MyThreadPool.getInstance();
for (int i =0; i < 6;i++) {
Runnable runnable = new Runnable(){
@Override
public void run() {
try {
System.out.println("run thread id "+Thread.currentThread().getId());
Thread.sleep(1000 * 5);
System.out.println("stop thread id "+Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
System.out.println("thread pool add task "+ (i+1));
instance.execute(runnable);
}
//jvm的一个钩子函数,在jvm关闭的时候调用
Runtime.getRuntime().addShutdownHook(new Thread(()->{
System.out.println("jvm closing");
//关闭线程池,shutdown() 不接受新任务,正在执行中的任务继续执行。shutdownNow() 会调用interrupt() 终端线程执行,将未执行任务返回。
instance.shutdown();
while(true) {
try {
if (instance.awaitTermination(500, TimeUnit.MILLISECONDS)) {
System.out.println("thread pool is closed");
break;
}else {
System.out.println("wait pool closing");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}));
//为了查看结果,将线程阻塞在这里。
try {
TimeUnit.SECONDS.sleep(1000*300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyThreadPool {
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_NUMBER,
CORE_NUMBER,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory());
private MyThreadPool() {
}
public static ThreadPoolExecutor getInstance() {
return threadPoolExecutor;
}
}
}
线程池使用场景分类
(1) cpu 密集型
执行任务时,cpu的使用较高,任务执行较快。
一般为了避免cpu上下文来回切换,浪费时间,CoreThreadNumber设置为cpu核心数。
(2) io 密集型
执行任务时,cpu空闲时间比较多,大多时间在等待io操作,或者网络操作响应。
(3)混合型
这种情况属于介于1和2之间的一种类型。例如web服务器一次http请求响应就是比较典型的,既有一些逻辑计算处理又有网络请求,请求数据库。
混合型有一个公式可以计算最佳的核心线程数量
最佳核心线程数量=((线程等待时间/cpu执行时间)+1) * cpu 核心数