java线程池基础


一、线程池基础概念

1.1 什么是线程池?

线程池的基础概念

线程池(Thread Pool)是一种用于管理和复用线程的机制,它包含一组线程以及一些管理这些线程的方法。线程池在程序启动时创建一定数量的线程,并在需要执行任务时从线程池中获取线程来执行任务,任务执行完毕后线程并不立即销毁,而是返回线程池中等待下次任务执行。这样可以避免频繁地创建和销毁线程,提高系统的性能和效率。

1.2 为什么要用线程池?

为什么要用线程池?其实就是线程池优势在哪里?

1.降低资源消耗:线程的创建和销毁是一项消耗资源较大的操作,使用线程池可以减少这种开销。
2.提高响应速度:线程池中的线程可以立即执行任务,无需等待线程创建,提高系统的响应速度。
3.控制并发线程数量:通过线程池可以限制并发线程的数量,避免系统资源被耗尽。
4.提高系统稳定性:合理使用线程池可以避免因为线程过多导致系统崩溃的情况发生。

1.3 线程池的工作原理

线程池具体是如何工作的?
按照一个线程的运行过程,大致可以明白线程池的工作原理:

1.线程池初始化:根据配置信息(如核心线程数、最大线程数、任务队列等)创建一定数量的线程,并将这些线程保存在线程池中。
2.任务提交:当有任务需要执行时,线程池会从线程池中取出一个空闲线程来执行任务。
3.任务队列:如果线程池中的线程都在执行任务,新的任务会被放入任务队列中等待执行。
4.线程复用:当线程池中的线程空闲时,会从任务队列中取出任务来执行,实现线程的复用。
5.线程销毁:根据配置的参数,当线程空闲时间超过设定的时间时,空闲线程会被销毁,以节省资源。


二、java中的线程池

2.1 线程池真正实现类 ThreadPoolExecutor

2.1.2 构造器

参数不同,但最终都会调到最下方的构造器中

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

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

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

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

2.1.3 参数

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

corePoolSize、maximumPoolSize、keepAliveTime、unit不用太多说明,都是数字或者true、false,根据自己的需求填充对应的参数即可。

2.1.3.1 workQueue

用于指定线程池中保存等待执行任务的阻塞队列。在多线程编程中,线程池的工作原理之一就是通过任务队列来保存待执行的任务,以便线程池中的线程可以按需取出任务执行。

常见的阻塞队列类型包括:

LinkedBlockingQueue:基于链表实现的无界阻塞队列,当任务队列已满时会一直阻塞等待直到有空间。
ArrayBlockingQueue:基于数组实现的有界阻塞队列,需要指定队列的容量,当队列已满时会阻塞等待。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,常用于直接传递任务。
PriorityBlockingQueue:具有优先级的阻塞队列,根据元素的优先级顺序取出任务。
DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

有界队列与无界队列

有界队列:
指定了队列的最大容量,当队列已满时,新提交的任务将会被阻塞等待,直到队列中有空间可以存放任务。
有界队列可以避免无限制地接受任务导致内存溢出的风险,可以控制系统资源的使用。
使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略。
无界队列
无界队列没有指定固定的容量,可以不断地接受新的任务,直到系统资源耗尽。
无界队列可以保证任务不会被拒绝,但可能会导致内存溢出或系统资源耗尽的风险。
使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

选择合适的阻塞队列类型可以根据实际场景来决定,例如需要控制任务的执行顺序、任务的优先级等。阻塞队列的选择也会影响线程池的性能和行为,因此需要根据具体需求进行选择。

2.1.3.2 threadFactory

用于创建新线程的工厂。在线程池中,当需要创建新的线程来执行任务时,会使用threadFactory来创建线程实例。
自定义threadFactory可以通过实现ThreadFactory接口来实现,重写newThread方法来创建新线程。例如:

ThreadFactory customThreadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("CustomThread-" + thread.getId());
        thread.setPriority(Thread.MAX_PRIORITY);
        return thread;
    }
};

在实际应用中,通过自定义threadFactory可以实现一些特定的需求,如统一设置线程名称、设置线程优先级、设置线程为守护线程等。这样可以更好地管理线程池中的线程,使其符合系统需求和调优要求。

Executors 框架已经为我们实现了一个默认的线程工厂。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
    private 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() {
            @SuppressWarnings("removal")
            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;
        }
    }
2.1.3.3 handler

用于指定当线程池和任务队列都已满时,新的任务如何被拒绝执行的策略。在多线程编程中,当线程池无法接受新任务时,就会触发拒绝策略来处理这种情况。

常见的拒绝策略包括:

AbortPolicy:默认的拒绝策略,会直接抛出RejectedExecutionException异常,通知调用者任务被拒绝。
CallerRunsPolicy:将任务交给调用线程来执行,即在调用线程中执行被拒绝的任务。
DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理。
DiscardOldestPolicy:丢弃队列中等待时间最长的任务,然后尝试重新提交新的任务。

自定义拒绝策略:可以实现RejectedExecutionHandler接口来自定义拒绝策略,根据具体需求来处理被拒绝的任务。
选择合适的拒绝策略可以根据实际需求来决定,例如希望通知调用者任务被拒绝、希望在调用线程中执行被拒绝的任务、或者希望丢弃一些任务以保证系统的稳定性等。

2.2 线程池的使用

一大堆的概念看完了?那么线程池具体该如何使用呢?
其实java内部通过Executors提供了四种线程池,而内部都是使用了ThreadPoolExecutor

2.2.1 newCachedThreadPool

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

创建一个可缓存的线程池,适用于执行很多短期异步任务的场景。
如果线程池中有空闲线程,则重用空闲线程;如果没有空闲线程,则创建新线程。
当线程空闲时间超过60秒时,会被回收,适用于执行大量短期异步任务的情况。

2.2.2 newFixedThreadPool

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

创建一个固定大小的线程池,适用于控制线程数量的场景。
线程池中始终保持固定数量的工作线程,当有任务提交时,如果有空闲线程则立即执行,否则任务进入队列等待。
适用于需要限制线程数量的情况,避免线程过多导致资源消耗过大。

2.2.3 newScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

创建一个固定大小的线程池,支持定时及周期性任务执行。
可以设置核心线程数,适用于需要定时执行任务的场景,如定时任务、周期性任务等。
可以通过schedule方法或scheduleAtFixedRate方法执行定时任务。

2.2.4 newSingleThreadExecutor

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

创建一个只有一个工作线程的线程池,适用于需要顺序执行任务的场景。
线程池中只有一个工作线程,保证任务按照提交的顺序依次执行。
适用于需要按顺序执行任务、避免并发执行的场景。

    public static void main(String[] args) {
        cachedThreadPoolTest();
        fixedThreadPoolTest();
        ScheduledExecutorServiceTest();
        singleThreadExecutorTest();
    }

    public static void cachedThreadPoolTest() {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(() -> {
            System.out.println("Task 1");
        });
        cachedThreadPool.execute(() -> {
            System.out.println("Task 2");
        });
    }


    public static void fixedThreadPoolTest() {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        fixedThreadPool.execute(() -> {
            System.out.println("Task 1");
        });
        fixedThreadPool.execute(() -> {
            System.out.println("Task 2");
        });
    }

    public static void ScheduledExecutorServiceTest() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
        scheduledThreadPool.schedule(() -> {
            System.out.println("Scheduled Task 1");
        }, 3, TimeUnit.SECONDS);
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            System.out.println("Scheduled Task 2");
        }, 0, 5, TimeUnit.SECONDS);
    }

    public static void singleThreadExecutorTest() {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(() -> {
            System.out.println("Task 1");
        });
        singleThreadExecutor.execute(() -> {
            System.out.println("Task 2");
        });
    }

在这里插入图片描述

2.3 方法对比

方法特点优点缺点适用场景
newCachedThreadPool线程池中的线程数量可以动态调整。能够根据实际需求动态调整线程数量,节省资源。可能会因为线程数量过多导致系统资源消耗过大。适用于执行大量短期异步任务的场景。
newFixedThreadPool线程池中的线程数量是固定的。能够限制线程数量,避免线程过多导致资源消耗过大。可能会因为线程数量有限而无法满足高并发需求。适用于控制线程数量的场景。
newScheduledThreadPool支持定时及周期性任务执行,可以设置核心线程数,最大线程数和线程空闲时间等参数。能够方便地执行定时任务、周期性任务等。可能会因为定时任务过多导致线程池负载过重。适用于需要定时执行任务的场景。
newSingleThreadExecutor只有一个工作线程的线程池能够保证任务按照提交的顺序依次执行,避免并发执行。可能会因为只有一个线程而无法满足高并发需求。适用于需要顺序执行任务的场景。

2.4 使用流程

// 1. 创建线程池
   // 创建时,通过配置线程池的参数,从而实现自己所需的线程池
   Executor threadPool = new ThreadPoolExecutor(
                                              CORE_POOL_SIZE,
                                              MAXIMUM_POOL_SIZE,
                                              KEEP_ALIVE,
                                              TimeUnit.SECONDS,
                                              sPoolWorkQueue,
                                              sThreadFactory
                                              );
    // 注:在Java中,已内置4种常见线程池,下面会详细说明

// 2. 向线程池提交任务:execute()
    // 说明:传入 Runnable对象
       threadPool.execute(new Runnable() {
            @Override
            public void run() {
                ... // 线程执行任务
            }
        });

// 3. 关闭线程池shutdown() 
  threadPool.shutdown();
  
  // 关闭线程的原理
  // a. 遍历线程池中的所有工作线程
  // b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)

  // 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
  // 二者区别:
  // shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
  // shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
  // 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

三、内部原理逻辑

在这里插入图片描述
此图来自大神的简书
Android多线程:线程池ThreadPool全面解析


四、参考链接

Android多线程:线程池ThreadPool全面解析
java线程池使用最全详解
Java 多线程:彻底搞懂线程池

在这里插入图片描述

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值