Java中线程池简介

当程序中线程创建过多或频繁创建/删除线程,需要消耗服务器大量资源,且易造成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核心&非核心核心固定/非核心不固定非核心线程闲置时,会被立即回收执行定时/周期任务
newScheduledThreadPool非核心不固定优先使用闲置线程处理任务 / 无线程使用,则新建线程 / 灵活回收空置线程执行量多,耗时少的任务
newSingleThreadExecutor核心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

public class ThreadTest {
    // 任务数
    private static final int taskCount = 31;

    public static void main(String[] args) {

        AtomicInteger integer = new AtomicInteger();

        // 创建一个线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                10,
                20,
                5,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(30),
                r -> new Thread(r, "test-thread-" + integer.get()));

        System.out.println("总任务数: " + taskCount);

        long start = System.currentTimeMillis();

        // 模拟任务提交
        for (int i = 0; i < taskCount; i++) {
            Thread thread = new Thread(() -> {
                try {
                    // 模拟任务执行
                    Thread.sleep(500);
                    System.out.println("当前线程为: "+Thread.currentThread().getName()+"执行第" + integer.addAndGet(1) + "个任务");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            try {
                // 拒绝策略会报错
                executor.execute(thread);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        long end = 0;
        while (executor.getCompletedTaskCount() < taskCount) {
            end = System.currentTimeMillis();
        }
        System.out.println("任务总耗时: " + (end - start));
    }
}

运行结果

    /**
     * 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
     * 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
     * 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
     * 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
     * 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常
     *
     *
     * 记住一句话
     * 任务数 <= 核心线程数时,线程池中工作线程数 = 任务数
     * 核心线程数 + 队列容量 < 任务数 <= 最大线程数 + 队列容量时,工作线程数 = 任务数 - 队列容量
     *
     *
     * 总任务数: 5
     * 执行第1个任务
     * 执行第5个任务
     * 任务总耗时: 541     任务数小于核心线程数 , 任务立即执行 , 且是异步执行, 所以500ms 代码模拟需要花费时间, 所以总用时541
     *
     * 总任务数: 10
     * 执行第1个任务
     * 执行第10个任务
     * 任务总耗时: 545     任务数等于核心线程数,没超过核心线程数, 任务立即执行 , 且是异步执行, 所以500ms
     *
     *
     * 总任务数: 11
     * 执行第1个任务
     * 执行第11个任务
     * 任务总耗时: 1046    任务数大于核心线程数,超过核心线程数, 10个任务立即执行 , 第11个任务会放到队列中, 等线程有空再执行, 即 10+1 总用时1000
     *
     *
     * 总任务数: 20
     * 执行第1个任务
     * 执行第20个任务
     * 任务总耗时: 1044    任务数大于核心线程数,超过核心线程数, 10个任务立即执行 , 剩下的任务会放到队列中, 等线程有空再执行, 即 10+10 总用时1000
     *
     *
     * 总任务数: 30
     * 执行第1个任务
     * 执行第30个任务
     * 任务总耗时: 1547    任务数大于核心线程数,超过核心线程数, 10个任务立即执行 , 剩下的任务会放到队列中, 等线程有空再执行,每次都执行十个 即 10+10+10 总用时1500
     *
     * 总任务数: 35
     * 执行第1个任务
     * 执行第35个任务
     * 任务总耗时: 2051    任务数大于核心线程数,超过核心线程数, 10个任务立即执行 , 剩下的任务会放到队列中, 等线程有空再执行,每次都最大 即 10+10+10+5 总用时2000
     *
     * 总任务数: 40
     * 执行第1个任务
     * 执行第40个任务
     * 任务总耗时: 2070    任务数大于核心线程数,超过核心线程数, 10个任务立即执行 , 剩下的任务会放到队列中, 等线程有空再执行,每次都最大 即 10+10+10+10 总用时2000
     *
     *
     * 总任务数: 41
     * 执行第1个任务
     * 执行第41个任务
     * 任务总耗时: 2056    任务数大于 (核心线程数 +  队列数) , 每次执行为 41-30 = 11, 执行计划 11+11+11+8  总用时2000
     *
     *
     *
     * 总任务数: 44
     * 执行第1个任务
     * 执行第44个任务
     * 任务总耗时: 2058    任务数大于 (核心线程数 +  队列数) , 每次执行为 44-30 = 14, 执行计划  14+14+14+2  总用时2000
     *
     *
     * 总任务数: 50
     * 执行第1个任务
     * 执行第50个任务
     * 任务总耗时: 1558    任务数大于 (核心线程数 +  队列数) , 每次执行为 50-30 = 20,  执行计划 20+20+10  总用时 1500
     *
     */

参考资料:
https://blog.csdn.net/qq_44918331/article/details/114372078
https://jimmysun.blog.csdn.net/article/details/95225769
https://blog.csdn.net/qq_32748869/article/details/107511616

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java线程池的参数包括以下7个: 1. corePoolSize:线程池的基本大小,即在没有任务需要执行的时候线程池的大小。 2. maximumPoolSize:线程池最大的大小,即线程池允许的最大线程数。 3. keepAliveTime:线程池的线程空闲后,保持存活的时间。 4. unit:keepAliveTime的时间单位。 5. workQueue:任务队列,用于保存等待执行的任务的阻塞队列。 6. threadFactory:线程工厂,用于创建新线程。 7. handler:拒绝策略,用于当任务队列已满,且线程池的线程数达到maximumPoolSize时,如何拒绝新任务的策略。 下面是一个示例代码,展示了如何使用Java线程池参数: ```java import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // 创建一个线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // corePoolSize 4, // maximumPoolSize 60, // keepAliveTime TimeUnit.SECONDS, // unit new ArrayBlockingQueue<Runnable>(4), // workQueue Executors.defaultThreadFactory(), // threadFactory new ThreadPoolExecutor.AbortPolicy() // handler ); // 提交任务 for (int i = 0; i < 10; i++) { executor.execute(new Task(i)); } // 关闭线程池 executor.shutdown(); } static class Task implements Runnable { private int num; public Task(int num) { this.num = num; } @Override public void run() { System.out.println("正在执行task " + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task " + num + "执行完毕"); } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值