阿里巴巴不推荐使用JDK自带工具类创建线程池的原因

一、线程和线程池的关系

    /**
     * 使用list来创建100个线程 花费时间 220 ms
     *
     * @throws InterruptedException
     */
    private static void createThreadPoolForList() throws InterruptedException {
        List<Thread> threadPool = new ArrayList<>(100);
        long start = System.currentTimeMillis();
        logger.info("创建线程池开始");
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t" + i);
            thread.start();
            threadPool.add(thread);
            TimeUnit.MICROSECONDS.sleep(1);
        }
        logger.info("创建100个线程的线程池花费时间:{} ms",System.currentTimeMillis()-start);
    }

创建100个线程花费了220ms,一个线程大概是2.2ms。但是任务执行,可能只是在1ms,那么这样子以来就得不偿失了。

所以临时创建线程来执行任务是不划算的。

而且在创建线程期间,如果存在大量的创建线程,会导致频繁的用户态到内核态的切换。非常消耗性能。

所以为此切记不要在每次执行任务的时候而来创建线程池执行任务。

而对于已经创建好了线程来说,我们可以重复利用这里的线程,让线程为具体执行的业务而来执行对应的代码。不需要每次执行任务而来创建一个线程来执行,高效利用已经存在了的线程。

线程复用

利用已经存在了的线程来执行任务,那么线程池中的一个线程可能是执行了多个任务。所以我们从打印上效果上来看,就可以看到具体的信息。

二、阿里巴巴为什么不推荐使用JDK自带工具类创建线程池

newCachedThreadPool因为大小不确定而占用内存;newFixedThreadPool和newSingleThreadExecutor因为队列占用CPU资源;所以不推荐

ExecutorService executorService1 = Executors.newCachedThreadPool();//最快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢


// 创建一个定长线程池,支持定时及周期性任务执行
// 我觉得是不支持自定义化操作
Executors.newScheduledThreadPool(10);

Executors.newCachedThreadPool()

原理需要看下对应的源码分析

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

没有核心线程,只有Integer.MAX_VALUE个非核心线程。每进来一个核心线程,就会进入到队列中来,但是队列只能够处理1个。处于忙碌状态,所以只能够创建非核心线程来进行处理。

弊端:不断的创建对象,会导致内存溢出

Executors.newFixedThreadPool(10)

进入newFixedThreadPool(10)方法,方法传递了一个参数10,可以看到里面调用了原生创建线程池方法ThreadPoolExecutor,10通过源码我们可以看到赋值了核心线程和最大线程数,证明有10个核心线程来执行此任务,最大线程也是10,就没有临时线程了,所有任务全部用这10个核心线程来执行,因为线程有限,所以执行速度较慢。

public static ExecutorService newFixedThreadPool(int nThreads) {    
return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

没有临时线程,只有核心线程和非核心线程。核心队列处理不过来,就只能够放入到队列中来。

弊端:假设我们设置了线程数为一个预测并发最高值,但是在双十一当天并发比原来的高的多,线程处理不过来,请求任务放在队列中,过多导致CPU突增,产生任务尖刺。

Executors.newSingleThreadExecutor()

进入newSingleThreadExecutor()方法,可以看到里面调用了原生创建线程池方法ThreadPoolExecutor,传递了不同的参数,通过源码可以看到核心线程和最大线程为1,证明只有一个核心线程,没有临时线程,因为线程只有1个,所有执行速度最慢。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

弊端:当请求任务增量特别大的时候,由于线程数为1,请求任务全部在队列里面,会导致内存溢出

所以阿里开发手册上写了不建议使用Executors,Java常用的线程池弊端在上面也介绍了,解决方法是我们进行自定义线程池,设置线程数。

posted @ 2022-10-26 09:16  写的代码很烂  阅读(753)  评论(0编辑  收藏  举报