一、核心构造方法参数讲解
参数名 | 作用 |
corePoolSize | 核心线程池大小 |
maximumPoolSize | 最大线程池大小 |
keepAliveTime | 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间 |
TimeUnit | keepAliveTime时间单位 |
workQueue | 阻塞任务队列 |
threadFactory | 新建线程工厂 |
RejectedExecutionHandler | 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理 |
重点讲解:
其中比较容易让人误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
定制属于自己的非阻塞线程池
@Slf4j
public class CustThreadPoolExecutor {
private ThreadPoolExecutor pool = null;
private static final int CORE_POOL_SIZE = 10;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_TIME = 30;
/**
* 线程池初始化方法
*
* corePoolSize 核心线程池大小----10
* maximumPoolSize 最大线程池大小----20
* keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间----30+单位TimeUnit
* TimeUnit keepAliveTime时间单位----TimeUnit.MINUTES
* workQueue 阻塞队列----new ArrayBlockingQueue<>(10)====10容量的阻塞队列
* threadFactory 新建线程工厂----new CustomThreadFactory()====定制的线程工厂
* rejectedExecutionHandler 当提交任务数超过maxmumPoolSize+workQueue之和时,
* 即当提交第31个任务时(前面线程都没有执行完,此测试方法中用sleep(100)),
* 任务会交给RejectedExecutionHandler来处理
*/
private void init() {
pool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.MINUTES,
new ArrayBlockingQueue<>(10),
new CustomThreadFactory(),
new CustomRejectedExecutionHandler());
}
public void destroy() {
if(pool != null) {
//pool.shutdownNow(); // 立刻关闭线程池,提交的任务没有全部执行
pool.shutdown(); // 优雅关闭线程池,没有被拒绝的任务都可执行
}
}
public ExecutorService getCustThreadPoolExecutor() {
return this.pool;
}
// 自定义线程工厂
private class CustThreadFactory implements ThreadFactory {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
String threadName = CustThreadPoolExecutor.class.getSimpleName() + count.addAndGet(1);
System.out.println(threadName);
t.setName(threadName);
return t;
}
}
// 实现RejectedExecutionHandler接口,自定义拒绝策略
private class CustRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录异常、报警处理等
//log.info("ERROR..................................................");
System.out.println("ERROR..................................................");
}
}
// 测试构造的线程池
public static void main(String[] args) {
CustThreadPoolExecutor exec = new CustThreadPoolExecutor();
// 1.初始化
exec.init();
// 2、执行任务
ExecutorService pool = exec.getCustThreadPoolExecutor();
for(int i=1; i<50; i++) {
//log.info("提交第" + i + "个任务!");
System.out.println("提交第" + i + "个任务!");
pool.execute(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
//e.printStackTrace();
}
System.out.println("task running=====");
});
}
// 3.销毁
exec.destroy();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
方法中建立一个核心线程数为10个,缓冲队列有10个的线程池。每个线程任务,执行时会先睡眠3秒,保证提交10任务时,线程数目被占用完,再提交20任务时,阻塞队列被占用完,这样提交第31个任务是,会交给CustRejectedExecutionHandler 异常处理类来处理。
public void execute(Runnable command) {
// 如果提交的任务为空,则抛出空指针异常
if (command == null)
throw new NullPointerException();
1、
int c = ctl.get();// 获取线程池的状态和线程池中线程的数量
if (workerCountOf(c) < corePoolSize) {// 线程池中的线程数量小于corePoolSize的值
// 重新开启线程执行任务
if (addWorker(command, true))
return;
c = ctl.get();
}
2、// 如果线程池处于RUNNING状态,并且入队成功,即队列未满
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();// 再次获取线程池的状态和线程池中线程的数量,二次检查
// 如果线程池没有未处于RUNNING状态,回滚,从队列中删除任务
if (! isRunning(recheck) && remove(command))
reject(command);// 执行拒绝策略
else if (workerCountOf(recheck) == 0)
addWorker(null, false);// 如果线程池为空,则向线程池中添加一个线程
}
3、// 任务队列已满,则尝试新增worker线程,如果新增线程失败,则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
注意:31以后提交的任务就不能正常处理了。因为,execute中提交到任务队列是用的offer方法,如上面代码,这个方法是非阻塞的,所以就会交给CustRejectedExecutionHandler 来处理。所以对于大数据量的任务来说,这种线程池,如果不设置队列长度会OOM。设置队列长度,会有任务得不到处理,接下来我们构建一个阻塞的自定义线程池
定制属于自己的阻塞线程池
public class CustThreadPoolExecutor2 {
private ThreadPoolExecutor pool = null;
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 3;
private static final int KEEP_ALIVE_TIME = 30;
/**
* 线程池初始化方法
*/
private void init() {
pool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.MINUTES,
new ArrayBlockingQueue<>(10),
new CustomThreadFactory(),
new CustomRejectedExecutionHandler());
}
public void destroy() {
if(pool != null) {
pool.shutdown();
}
}
public ExecutorService getCustThreadPoolExecutor() {
return this.pool;
}
private class CustThreadFactory implements ThreadFactory {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
String threadName = CustThreadPoolExecutor.class.getSimpleName() + count.addAndGet(1);
System.out.println(threadName);
t.setName(threadName);
return t;
}
}
private class CustRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// 核心改造点,由blockingQueue的offer改阻塞成put阻塞方法
// 如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续
// 这样所提交的任务全部有机会执行
executor.getQueue().put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 测试构造的线程池
public static void main(String[] args) {
CustThreadPoolExecutor2 exec = new CustThreadPoolExecutor2();
// 1.初始化
exec.init();
ExecutorService pool = exec.getCustThreadPoolExecutor();
for(int i=1; i<=40; i++) {
System.out.println("提交第" + i + "个任务!");
pool.execute(() -> {
try {
System.out.println(">>>task is running=====");
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
exec.destroy();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
源码:当提交任务被拒绝时,进入拒绝机制,我们实现拒绝方法,把任务重新用阻塞提交方法put提交,实现阻塞提交任务功能,防止队列过大,OOM,提交被拒绝方法在下面
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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))
// 进入拒绝机制, 我们把runnable任务拿出来,重新用阻塞操作put,来实现提交阻塞功能
reject(command);
}