Java中线程池简介

1,130 阅读4分钟

当程序中线程创建过多或频繁创建/删除线程,需要消耗服务器大量资源,且易造成OOM,所以人们,创建了线程池来管理线程的创建和销毁.

1 常见的四种创建线程池方法

使用Executors类提供的方法,很容易获取线程池.

tips: alibaba编码规范手册上不推荐使用该方法,建议使用ThreadPoolExecutor方法,下章讲.

1 newFixedThreadPool(固定大小的线程池)

    @Test
    public void newFixedThreadPoolTest() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        
        for (int i = 0; i < 3; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(new Date() +Thread.currentThread().getName());
                        //线程睡眠
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        //线程睡眠  防止线程执行前就结束
        Thread.sleep(3000);
         executorService.shutdown();
    }

运行结果:

Mon Mar 22 20:37:57 CST 2021pool-1-thread-1
Mon Mar 22 20:37:57 CST 2021pool-1-thread-2
Mon Mar 22 20:37:58 CST 2021pool-1-thread-1

说明: 创建一个线程数量为2的线程池,使用for循环提交了三个任务,每个任务睡眠一秒,前面只有两个任务拿到线程执行,后面等下一轮线程执行.且执行的时间差为1秒.

2 newCachedThreadPool(可缓存的线程池)

    @Test
    public void newCachedThreadPoolTest() throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 3; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(new Date() +Thread.currentThread().getName());
                        //线程睡眠
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        //线程睡眠  防止线程执行前就结束
        Thread.sleep(1000);
         executorService.shutdown();
    }

运行结果:

Mon Mar 22 22:06:06 CST 2021pool-1-thread-2
Mon Mar 22 22:06:06 CST 2021pool-1-thread-1
Mon Mar 22 22:06:06 CST 2021pool-1-thread-3

**说明: **创建一个可以缓存的线程池,对线程数没有限制,可创建JVM能允许的最大线程数量.

3 newSingleThreadExecutor(单线程的线程池)

    @Test
    public void newSingleThreadExecutorTest() throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 3; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(new Date() +Thread.currentThread().getName());
                        //线程睡眠
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        //线程睡眠  防止线程执行前就结束
        Thread.sleep(4000);
        executorService.shutdown();
    }

运行结果:

Mon Mar 22 22:11:00 CST 2021pool-1-thread-1
Mon Mar 22 22:11:01 CST 2021pool-1-thread-1
Mon Mar 22 22:11:02 CST 2021pool-1-thread-1

说明: 单个线程执行所有任务,类似单线程执行.

4 newScheduledThreadPool(定时及周期性任务的线程池)

==定时执行==

    @Test
    public void newScheduledThreadPoolTest() throws InterruptedException {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
        System.out.println("开始时间"+new Date());
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("线程执行时间"+new Date() + Thread.currentThread().getName());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 1, TimeUnit.SECONDS);

        //线程睡眠  防止线程执行前就结束
        Thread.sleep(3000);
        executorService.shutdown();
    }

运行结果:

开始时间Mon Mar 22 22:24:18 CST 2021
线程执行时间Mon Mar 22 22:24:19 CST 2021pool-1-thread-1

说明: 创建一个固定大小为2的线程池,设置延迟1秒执行任务.

==周期执行==

    @Test
    public void newScheduledThreadPoolTest() throws InterruptedException {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        System.out.println("开始时间"+new Date());
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("线程执行时间"+new Date() + Thread.currentThread().getName());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 1,3, TimeUnit.SECONDS);

        //线程睡眠  防止线程执行前就结束
        Thread.sleep(8000);
        executorService.shutdown();
    }

运行结果:

开始时间Mon Mar 22 22:28:23 CST 2021
线程执行时间Mon Mar 22 22:28:24 CST 2021pool-1-thread-1
线程执行时间Mon Mar 22 22:28:27 CST 2021pool-1-thread-1
线程执行时间Mon Mar 22 22:28:30 CST 2021pool-1-thread-2

说明: 创建一个线程数为3的周期线程池,设置每个一秒执行一次任务,总共执行三次.

总结:

线程池类型线程类型数量特点应用
newFixedThreadPool核心固定核心线程处于空闲状态,不会被回收控制线程最大并发数
newCachedThreadPool核心&非核心核心固定/非核心不固定非核心线程闲置时,会被立即回收执行定时/周期任务
newSingleThreadExecutor非核心不固定优先使用闲置线程处理任务 / 无线程使用,则新建线程 / 灵活回收空置线程执行量多,耗时少的任务
newScheduledThreadPool核心1所有任务按照指定顺序在一个线程中执行单线程
  • FixedThreadPoolSingleThreadExecutor线程池中,使用的是LinkedBlockingQueue阻塞队列,请求处理堆积,可能会耗费很多内存,甚至OOM.

  • CachedThreadPoolScheduledThreadPool线程池,主要是线程最大数量是Integer.MAX_VALUE,可能创建巨量的线程,导致OOM.

==综上等原因:== alibaba编程手册建议不使用上述方法创建线程池.而是使用ThreadPoolExecutor类创建线程池.

2 ThreadPoolExecutor类创建线程池

1 ThreadPoolExecutor常用构造方法

//构造1
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

//构造2
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

//构造3
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

//构造4
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数说明:

参数名称参数说明
corePoolSize核心线程数
maximumPoolSize线程池最大线程数
workQueue任务队列
keepAliveTime线程闲置超时时间
TimeUnit指定keepAliveTime的时间单位
threadFactory(可选)线程工厂
handler(可选)拒绝策略

2 线程池参数说明

1 workQueue

任务队列: 基于阻塞队列实现的,采用生产者和消费者模式.

队列接口为BlockingQueue,常见阻塞实现如下:

  • ArrayBlockingQueue 由数组结构组成的有界阻塞队列

  • LinkedBlockingQueue 链表结构组成的有界阻塞队列,未指定容量,默认Integer最大值

  • PriorityBlockingQueue 支持优先级排序的无界阻塞队列

  • DelayQueue 无界优先级阻塞队列,通过执行时延从队列中提取任务

  • SynchronousQueue 不存储元素的阻塞队列

  • LinkedBlockingDeque 双向队列实现的有界双端阻塞队列

  • LinkedTransferQueue 无界的阻塞队列

tips:

  • 有界: 当任务队列达到最大值且超过最大线程数,就会执行拒绝策略.

  • 无界: 任务队列可以一直添加任务

2 threadFactory

线程工厂: 创建线程的方式.

需要实现ThreadFactory接口,且实现**newThread(Runnable r)**方法

Executors中默认的线程工厂如下:

    /**
     * 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;
        }
    }

3 handler

拒绝策略: 线程池的线程达到最大线程时,需要执行拒绝策略.

需要实现RejectedExecutionHandler接口,并实现**rejectedExecution(Runnable r, ThreadPoolExecutor executor)**方法.

Executors中提供的4种拒绝策略:

  • AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
  • CallerRunsPolicy:由调用线程处理该任务
  • DiscardPolicy:丢弃任务,但是不抛出异常 (可以配合这种模式进行自定义的处理方式)
  • DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务
/**
 * A handler for rejected tasks that throws a
 * {@code RejectedExecutionException}.
 */
public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

    /**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }


    /**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }


    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

3 线程池执行流程图

image-20210328103708150