我们都知道在java语言中,开启线程是继承Thead或者实现Runnable接口,在高级一点我们使用Thread线程池。接下来,我要说的就是Thread线程池踩坑。
首先,需要注意的是什么场景需要使用线程池,使用哪个线程池,使用线程池需要注意哪些地方。
我们使用多线程是为了让多核cpu处理器更好的发光发热。如果是单核cpu开多了线程也不会有实际效果。所以我们在使用多线程要考虑机器的配置。一般情况,我们开cpu的2到3倍线程,开多了会增加系统调度开销处理不过来,开少了不能充分利用cpu。实际上,如果是耗时高的任务,可以这样处理。耗时短的比如过io操作,我们可以多开一些线程的。 还有,我们在服务中可以自定义一个全局线程池,这样子,可以更加方便的管理,更好的控制服务线程的数量。但是,如果有不同场景,比如耗时长的和耗时短的还是应该分成两个线程池单独管理比较好。下面我们看一下线程池使用方式。
线程池需要实现Executor接口,划重点(线程池大小,队列长度和拒绝策略)
Executors.defaultThreadFactory(); //默认工厂 Executors.newCachedThreadPool(); //根据需要无限阔成 Executors.newFixedThreadPool(); //创建固定数量线程池 Executors.newScheduledThreadPool() //创建定时器线程池
(1)ThreadPoolExecutor
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 60L, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy()); 还有其他构造方式就不一一介绍了
(2)使用Spring线程池AsyncTaskExecutor
@Configuration @EnableAsync public class ThreadPoolConfiguration { @Bean @Primary public AsyncTaskExecutor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程数量:低于此值会重新创建 executor.setCorePoolSize(5); //任务队列:核心线程处于工作中,会先放到队列中缓存 executor.setQueueCapacity(1000); //线程池最大数量:任务队列已满时,最多创建的线程数量 executor.setMaxPoolSize(100); //线程超时时间:超出核心线程数量的线程,在空闲一定的时间后退出 executor.setKeepAliveSeconds(30); //线程池已满,队列已满时。新的任务处理策略:丢弃、执行、忽略、 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } }
/** * The default thread factory */ static class DefaultThreadFactory 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; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } 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; } }
这是线程池默认工厂类,我们可以把它重写自定义,一般就是修改一下名字,日志啥的。
我们在使用的时候,一般只注意线程的数量,最大线程数量,线程存活时间参数。 忘掉最重要两个参数, 阻塞队列和线程池拒绝策略。ThreadPoolExecutor线程池队列默认是无界队列如果我们不设置长度, 极端情况会把我们系统给撑死。ok,为了不把服务累死,我们把队列长度设置了, 此时,拒绝策略开始发挥作用了。默认拒绝策略是 AbortPolicy:这样当线程池满了,队列满了, 会把任务丢弃适合允许丢失追求性能的场景。 //线程池已满,队列已满时。新的任务处理策略:丢弃、执行、忽略 //AbortPolicy:丢弃 //DiscardPolicy:忽略 //CallerRunsPolicy:立即运行 //DiscardOldestPolicy:压进队列最后一位(踢出第一位)
切记不用忘了设置队列长度,和拒绝策略防止数据丢失
个人博客地址