JDK线程池

实际生产中,我们需要手写一个线程池来减少线程的创建释放。

常见的创建方式:

newFixedThreadPool 该方法返回一个固定线程数量的线程池
newSingleThreadExecutor 该方法返回一个只有一个现成的线程池
newCachedThreadPool 返回一个可以根据实际情况调整线程数量的线程池
newSingleThreadScheduledExecutor 该方法和newSingleThreadExecutor的区别是给定了时间执行某任务的功能,可以进行定时执行等;
newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行,可以指定线程数量

创建方式Executors.xxx

public class ThreadPoolDemo {
    public static void main(String[] args) {

        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i=0; i < 10; i++){
            int index = i;
            executorService.submit(() -> System.out.println("i:" + index + " executorService"));
        }
        executorService.shutdown();
    }

}

问题:

submit(Runnable task)方法提交一个线程
* 1)newFixedThreadPool和newSingleThreadExecutor:
* 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
* 2)newCachedThreadPool和newScheduledThreadPool:
* 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

解决方案:

使用ThreadPoolExecutor 构建线程池。

public class ThreadPoolDemo2 {
    /**
     *  corePoolSize 核心线程池大小
     *  maximumPoolSize 线程池最大容量大小;
     *  keepAliveTime线程池空闲时,线程存活的时间;
     *  TimeUnit 时间单位;
     *  workQueue 任务队列;
     *  handler线程拒绝策略;
     * @param args
     */
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(20, 40, 0L, 
                TimeUnit.MILLISECONDS, 
                new LinkedBlockingQueue<>(10), 
                Executors.defaultThreadFactory(), 
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 10; i++) {
            int index = i;
            executorService.submit(() -> {
                System.out.println("i:" + index + " executorService");
                try {
                    Thread.sleep(20000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
           );
        }
        executorService.shutdown();
    }
}

自定义ThreadFactory、自定义线程拒绝策略。

public class ThreadPoolDemo3 {
    /**
     *  corePoolSize 核心线程池大小
     *  maximumPoolSize 线程池最大容量大小;
     *  keepAliveTime线程池空闲时,线程存活的时间;
     *  TimeUnit 时间单位;
     *  workQueue 任务队列;
     *  handler线程拒绝策略;
     */
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10), new ThreadFactory() { // 自定义ThreadFactory
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName(r.getClass().getName());
                        return thread;
                    }
                }, new ThreadPoolExecutor.AbortPolicy());// 自定义线程拒绝策略

        for (int i = 0; i < 10; i++) {
            int index = i;
            /**
             * 发现没报异常,少了0;或者使用get()接收。阻塞当前线程直到任务完成。
             * 而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
             */
            Future<?> future = executorService.submit(() -> System.out.println(1/index + " index:" + index));
            try {
                future.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
            //使用execute代替。
//          executorService.execute(() -> System.out.println(1/index + " index:" + index));
        }
        executorService.shutdown();
    }
}

坑点:

  • submit的坑:
    使用submit提交task的时候,当里面某条线程有异常的情况下,没有异常抛出。
    解决方案:使用execute代替。或者使用Future接受返回结果,再get异常出来。

参数意义

ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,                          //核心线程数
MAXIMUM_POOL_SIZE,                       //线程池所容纳最大线程数(workQueue队列满了之后才开启)
KEEP_ALIVE,                              //非核心线程闲置时间超时时长 unit:keepAliveTime的单位
TimeUnit.SECONDS,                        //时间单位
new ArrayBlockingQueue<Runnable>(40),    //等待队列,存储还未执行的任务
Executors.defaultThreadFactory(),        //线程默认工厂
new ThreadPoolExecutor.AbortPolicy());   //线程拒绝策略

运行过程:
1. 线程池刚创建时,里面没有一个线程。
2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
  a、如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
  b、如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列
  c、如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
  d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3 、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4 、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

经过jmeter压力测试亲测:最大线程总数为maximumPoolSize+队列数目。

拒绝策略:

当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。

拒绝策略 含义
ThreadPoolExecutor.AbortPolicy(默认) 处理程序遭到拒绝将抛出运行时 RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy 线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度
ThreadPoolExecutor.DiscardPolicy 不能执行的任务将被删除。
ThreadPoolExecutor.DiscardOldestPolicy 如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

综合考虑:使用CallerRunsPolicy 起码本线程会执行,好过直接丢弃。

附录:

生产环境使用的线程池工具类。

public class ThreadPool  {
    /**
     * 说明:下面这些常量我是根据AsyncTask的源码配置的,大家可以根据自己需求自行配置
     */
    // 根据cpu的数量动态的配置核心线程数和最大线程数
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // 核心线程数 = CPU核心数 + 1
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    // 线程池最大线程数 = CPU核心数 * 2 + 1
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    // 非核心线程闲置时超时1s
    private static final int KEEP_ALIVE = 1;
    // 要确保该类只有一个实例对象,避免产生过多对象消费资源,所以采用单例模式
    private ThreadPool() {
        System.out.println("------------------核心线程数 = CPU核心数 + 1" + CORE_POOL_SIZE +"--------------");
    }

    private static ThreadPool sInstance;

    public synchronized static ThreadPool getsInstance() {
        if (sInstance == null) {
            sInstance = new ThreadPool();
        }
        return sInstance;
    }

    // 线程池的对象
    private ThreadPoolExecutor executor;

    // 使用线程池,线程池中线程的创建完全是由线程池自己来维护的,我们不需要创建任何的线程
    // 我们所需要做的事情就是往这个池子里面丢一个又一个的任务
    public void execute(Runnable r) {
        if (executor == null) {
            /**
             * corePoolSize:核心线程数
             * maximumPoolSize:线程池所容纳最大线程数(workQueue队列满了之后才开启)
             * keepAliveTime:非核心线程闲置时间超时时长 unit:keepAliveTime的单位
             * workQueue:等待队列,存储还未执行的任务 threadFactory:线程创建的工厂 handler:异常处理机制
             * 
             */
            executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
                    new ArrayBlockingQueue<Runnable>(20), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy());
        }
        executor.execute(r);// 把一个任务丢到了线程池中
    }

    public void cancel(Runnable r) {
        if (r != null) {
            executor.getQueue().remove(r);// 把任务移除等待队列
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_35830949/article/details/80163920