Java线程池
线程池作用
线程池可以管理线程,减少不必要的线程创建与销毁,提高资源利用率;
提高响应速率,因为任务一进来就直接执行不需要创建线程(如果线程池有空闲线程);
执行定时任务
线程作用
创建多个线程来执行任务,可以将较低相关性的任务异步化提高响应效率
场景
- 最常见的比如操作日志的记录,日志记录和业务逻辑无关,所以可以异步化
- 拆分多个请求,比如要去多个系统中获取数据,可以异步化同时获取
- 实时性不高的通知等
线程
线程的创建方式
创建线程的方式别人一般说五种
- 继承Thread类
public class MyThreadA extends Thread{
@Override
public void run(){
}
}
public static void main(String[] args){
MyThreadA myThread = new MyThreadA();
myThread.start();
}
- 实现Runnable接口
public class MyThreadB implements Runnable{
@Override
public void run(){}
}
public static void main(String[] args){
MyThreadB myThread = new MyThreadB();
Thread thread = new Thread(myThread);
thread.start();
}
- 实现Callable接口
public class MythreadC implements Callable{
@Override
public String call() throws Exception {
return "";
}
}
public static void main(String[] args){
MythreadC myThread = new MythreadC ();
FutureTask<String> futuewTask = new FutureTask<String>(myThread);
Thread thread = new Thread(futuewTask);
thread.start();
}
- 线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
executor.submit(new MyThreadA());
- 匿名内部类
Thread thread = new Thread(()->System.out.println("hello world"));
thread.start();
一开始看到这种回答我是真的觉得有点搞笑,前三点姑且就不说了,后面用线程池执行一个Thread都能算一种,匿名内部类也能算一种就有点离谱了,按这种说法,submit的参数不同都能算不同的创建方式,匿名内部类也是,匿名Thread匿名Runnable匿名Callable是不是都算呢,自创一个继承Runnable接口是不是也算一种呢?
其实归根究底都是一种,就是重写run方法后调用Thread的start方法,第一种就不用说了,第二种重写了run然后当做参数用来创建Thread对象调用start,第三种虽然一开始是重写call方法,但是当做参数创建Future的过程其实就变成了Runable的实现对象了
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
第四种,无论你往submit里丢什么,最后都变成了Thread,Future之类的对象
比如最简单的ThreadPoolExecutor,你往submit里传Callable实例,它就帮你转成Future之后调用execute,execute又会把这个Runnable实例包装成Woker,Worker里的属性含有Thread,创建woker实例的时候会用这个入参来实例化一个Thread注入Worker然后用来执行,具体可以简单阅读ThreadPoolExecutor源码,主要留意worker的创建过程和thread执行
线程池主要参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数
- maxinumPoolSize:最大线程数
- keepAliveTime:存活时长,空闲时间超过这个时间的非核心线程会被销毁
- unit:时长的单位
- workQueue:阻塞队列
- threadFactory:线程工厂
- handler:拒绝策略,任务太多阻塞队列溢出时触发该策略
执行过程
当一个任务进入执行器时,会先检查当前线程数是否小于核心线程数,如果小于会直接创建一个线程然后执行该任务,否则进入阻塞队列,如果阻塞队列满了并且线程数小于最大线程数则创建新线程,否则对该任务执行拒绝策略
线程池类型
java.util.concurrent.Executors可以创建多种线程池,但是一般知道是什么类型就行,我们可以参照着创建自己的线程池,因为Executors直接创建的线程池有内存溢出问题
比如
newCachedThreadPool 它的阻塞队列是SynchronousQueue 每来一个任务都会直接丢给线程,如果此时没有空闲线程就会创建一个线程,因为最大线程数是最大Integer所以可能发生OOM
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
newFixedThreadPool 的核心线程和最大线程数一样,它的阻塞队列是没设置size的LinkedBlockingQueue如果执行速度小于任务创建速度队列的元素越来越多就有可能OOM
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor 和上面一样并且线程数更少更容易触发
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ScheduledThreadPoolExecutor 也是因为最大线程数的原因,并且队列是阻塞队列更容易触发
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
自定义线程池
按照需求选择适合的线程池
比如logThreadPool我是用来记录日志的,属于IO密集型所以取了两倍CPU核心数,又因为这些日志比较重要不希望丢失,所以拒绝策略是交给主线程处理
sendVideo因为业务需要延迟执行所以选择ScheduledThreadPoolExecutor
@Configuration
public class ThreadPoolConfig {
private static final Integer CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
private static final Integer MAXIMUM_POOL_SIZE = CORE_POOL_SIZE * 2;
@Bean(name = "logThreadPool")
public ThreadPoolExecutor logThreadPool() {
CustomNameThreadFactory logThread = new CustomNameThreadFactory("logThread");
return new ThreadPoolExecutor(
CORE_POOL_SIZE,//核心线程数配置的是CPU核心数
MAXIMUM_POOL_SIZE,//最大线程数配置的是CPU核心数*2,因为记录日志的操作相对于读操作还是比较小的,所以没有设置很大,避免过多线程频繁切换线程
1,//过时时间为1秒,空闲 1 秒的线程会被终止,减少资源浪费
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100),// 选择LinkedBlockingQueue是因为比ArrayBlockingQueue要高性能一点,因为link有读写两个锁,array只有一个锁
// link的插入删除比较快,因为是链表,array插入删除头尾快,中间慢因为是循环数组,删除中间要移动
// 大小设置为100,避免OOM
logThread,//自定义工厂,添加线程前缀,方便日志追踪
new ThreadPoolExecutor.CallerRunsPolicy());//CallerRunsPolicy,被拒绝的任务交给主线程处理,不拒绝不丢弃避免日志丢失
}
@Bean(name = "sendVideo")
public ScheduledThreadPoolExecutor sendVideo() {
CustomNameThreadFactory sendThread = new CustomNameThreadFactory("sendVideo");
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
0,//核心线程为0,是因为预安检这个业务不是一直在执行,避免线程浪费
sendThread,
new ThreadPoolExecutor.CallerRunsPolicy());
scheduledThreadPoolExecutor.setKeepAliveTime(5,//过期时间五分钟,因为货物打包到货板需要的时间约为五分钟,最大程度避免资源浪费
TimeUnit.MINUTES);
scheduledThreadPoolExecutor.setMaximumPoolSize(MAXIMUM_POOL_SIZE * 2);//最大线程为cpu核心数*2,IO密集,占用CPU计算资源少,快速);
return scheduledThreadPoolExecutor;
}
}
其中线程工厂也是自定义的为了更好的进行链路追踪
public class CustomNameThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final String namePrefix;
public CustomNameThreadFactory(String name) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = name+"-" +
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(@NotNull Runnable r) {
Thread t = new Thread(group, r,
namePrefix + UUID.randomUUID(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
使用案例
可以选择直接注入,然后调用execute,submit,schedule等方法进行执行,但是如果你的Runnable实例的实现的run方法没有进行异常捕捉的话可能到时候难以发现问题,但是在run中写try-catch又显得不太优雅
所以一般都是再写一个Util类,类中调用CompletableFuture的方法
public static CompletableFuture<Void> run(Runnable runnable, Function<Throwable, Void> fn) {
return CompletableFuture.runAsync(runnable, executor).exceptionally(fn);
}
CompletableFuture可以看这篇美团技术文章