java线程池

Java线程池

线程池作用

线程池可以管理线程,减少不必要的线程创建与销毁,提高资源利用率;
提高响应速率,因为任务一进来就直接执行不需要创建线程(如果线程池有空闲线程);
执行定时任务

线程作用

创建多个线程来执行任务,可以将较低相关性的任务异步化提高响应效率

场景

  1. 最常见的比如操作日志的记录,日志记录和业务逻辑无关,所以可以异步化
  2. 拆分多个请求,比如要去多个系统中获取数据,可以异步化同时获取
  3. 实时性不高的通知等

线程

线程的创建方式

创建线程的方式别人一般说五种

  1. 继承Thread类
public class MyThreadA extends Thread{
	@Override
	public void run(){
	}
}

public static void main(String[] args){
	MyThreadA myThread = new MyThreadA();
	myThread.start();
}
  1. 实现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();
}
  1. 实现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();
}
  1. 线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
executor.submit(new MyThreadA());
  1. 匿名内部类
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)
  1. corePoolSize:核心线程数
  2. maxinumPoolSize:最大线程数
  3. keepAliveTime:存活时长,空闲时间超过这个时间的非核心线程会被销毁
  4. unit:时长的单位
  5. workQueue:阻塞队列
  6. threadFactory:线程工厂
  7. 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可以看这篇美团技术文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值