多线程之线程池 · 下
前言
线程池中这块的内容确实要比我预期的多,当然也可能是我讲的太细了,所以比较费字,但是这样也好,不仅让各位小伙伴能更清楚相关逻辑和原理,而且对我而言,让我可以做到知其然知其所以然,从这个层面上来讲,我觉得一切都值得,甚至还有点意外的收获。
今天是线程池相关知识点收官之作,今天的内容完结后,基本上线程池这块的内容就可以告一段落了,今天主要从以下几方面开展:
- 线程池的其他参数
- 线程工厂
- 拒绝策略处理器
好了,话不多说,让我们直接开始吧。
线程池
线程工厂
先看第一个参数——线程工厂,这个参数的作用是创建线程。在最开始的时候,我们创建线程池的时候并没有指定这个参数,但是构造器内部会自动为我们引入默认的线程工厂:
下面我们就来看下默认的线程工厂是如何实现的:
在线程工厂中,主要有两部分的操作,第一部分就是设定创建线程时的信息,包括线程组、线程名称、堆栈大小;第二部分主要是设置线程的优先级,如果线程是守护线程的话,会把它修改为非守护线程(看到这里,我发现关于线程组、线程得好好补补课了,后面要跟进下)。
我们一般自定义线程工厂主要是为了修改线程名称,这样方便我们在排查问题的时候找到我们自己定义的线程池,我们要自顶一下线程工厂只需要冲洗ThreadFactory
的newThread
方法即可:
static class MyThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
MyThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "syske-" +
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
这里我就直接把默认线程工厂的实现copy
过来,然后只改了名字,然后在构造线程池的时候传入即可:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new MyThreadFactory());
然后运行下,我们就会发现线程名称已经变成我们修改之后的了:
当然如果只是单纯想修改线程池中线程名称,这样就太奢侈了,我们可以通过guava
提供的ThreadFactoryBuilder
直接修改(就是我们之前分享的guava
):
ThreadFactory build = new ThreadFactoryBuilder().setNameFormat("syske-task-%d").build();
当然,guava
还可以设定其他属性,甚至可以指定线程工厂,UncaughtExceptionHandler
表示未捕获到的异常处理器:
拒绝策略(饱和策略)
我们在之前的内容中说过,如果任务数超过corePoolSize + maximumPoolSize + workQueue.size()
,线程池就会报拒绝连接的错误,这个错误就是这里抛出了的,所以接下来我们要分享的就是线程池的最后一个参数——RejectedExecutionHandler
。
和线程工厂一样,在我们不传拒绝策略处理器的时候,其实构造方法内部为我们制定了默认的处理器:
默认拒绝策略处理器的实现也很简单:
也可以说是简单粗暴,直接就抛出了RejectedExecutionException
异常。
当然我们也可以定义自己的拒绝策略处理器:
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("线程池拒绝连接,资源已耗尽:r = " + r + ", executor = " + executor);
throw new RejectedExecutionException();
}
}
然后也是在构造线程池的时候传入:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new MyThreadFactory(), new MyRejectedExecutionHandler());
我们把工作对了调小,把循环次数调大,把休眠时间调长,然后运行就会触发饱和策略,进入我们的的rejectedExecution
方法:
根据我的测试,我发现如果在rejectedExecution
方法中直接抛出RejectedExecutionException
,会导致主线程进入阻塞状态,这样整个系统就卡死了
但如果不抛出RejectedExecutionException
异常的话,则不会导致阻塞:
所以考虑到实际应用情况,我觉得我们还是有必要重写rejectedExecution
方法的,而且我们在方法内部还可以做一些业务操作,比如线程池扩容,或者睡眠等待:
或者可以做一些业务告警等操作。
总结
关于线程池的创建、参数以及参数的作用,经过我们这两天的详细分析和演示,我想大家一定也对线程池有了新的认知,应该说在以后的工作和学习中,不论是线程池的使用还是解决线程池相关的问题,都可以站得更高,看的更远。
当然,最重要的是,这几天分享的内容都比较实用。明天我们会对线程池这块做一个小结,然后小结之外我们会有一些遗漏知识点的补充。好了,今天就先到这里吧!