图解Java线程池原理

本文详细探讨了Java线程池的工作原理,包括线程池的创建、任务调度、拒绝策略等关键概念。通过分析`Executors`提供的不同线程池工厂方法,阐述了线程池如何通过阻塞队列管理任务。此外,文章还讲解了线程池内部的`ThreadPoolExecutor`类,分析了其`execute`方法的执行流程,以及线程池状态的变迁。最后,讨论了四种内置的拒绝策略,并总结了线程池在系统性能和资源管理中的重要作用。
摘要由CSDN通过智能技术生成

什么是线程池?

为了避免频繁重复的创建和销毁线程,我们可以让这些线程进行复用,在线程池中,总会有活跃的线程在占用,但是线程池中也会存在没有占用的线程,这些线程处于空闲状态,当有任务的时候会从池子里面拿去一个线程来进行使用,当完成工作后,并没有销毁线程,而是将将线程放回到池子中去。

线程池主要解决两个问题:
一是当执行大量异步任务时线程池能够提供很好的性能。
二是线程池提供了一种资源限制和管理的手段,比如可以限制现成的个数,动态新增线程等。
​ -《Java并发编程之美》

上面内容出自《Java并发编程之美》这本书,第一个问题上面已经提到过,线程的频繁创建和销毁是很损耗性能的,但是线程池中的线程是可以复用的,可以较好的提升性能问题,线程池内部是采用了阻塞队列来维护Runnable对象。

(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

原理分析

JDK为我们封装了一套操作多线程的框架Executors,帮助我们可以更好的控制线程池,Executors下提供了一些线程池的工厂方法:

  • newFixedThreadPool:返回固定长度的线程池,线程池中的线程数量是固定的。
  • newCacheThreadPool:该方法返回一个根据实际情况来进行调整线程数量的线程池,空余线程存活时间是60s
  • newSingleThreadExecutor:该方法返回一个只有一个线程的线程池。
  • newSingleThreadScheduledExecutor:该方法返回一个SchemeExecutorService对象,线程池大小为1,SchemeExecutorService接口在ThreadPoolExecutor类和 ExecutorService接口之上的扩展,在给定时间执行某任务。
  • newSchemeThreadPool:该方法返回一个SchemeExecutorService对象,可指定线程池线程数量。

对于核心的线程池来说,它内部都是使用了ThreadPoolExecutor对象来实现的,只不过内部参数信息不一样,我们先来看两个例子:nexFixedThreadPoolnewSingleThreadExecutor如下所示:

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

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

由上面的线程池的创建过程可以看到它们都是ThreadPoolExecutor的封装,接下来我们来看一下ThreadPoolExecutor的参数说明:

参数名称 参数描述
corePoolSize 指定线程池线程的数量
maximumPoolSize 指定线程池中线程的最大数量
keepAliveTime 当线程池线程的数量超过corePoolSize的时候,多余的空闲线程存活的时间,如果超过了corePoolSize,在keepAliveTime的时间之后,销毁线程
unit keepAliveTime的单位
workQueue 工作队列,将被提交但尚未执行的任务缓存起来
threadFactory 线程工厂,用于创建线程,不指定为默认线程工厂DefaultThreadFactory
handler 拒绝策略

其中workQueue代表的是提交但未执行的队列,它是BlockingQueue接口的对象,用于存放Runable对象,主要分为以下几种类型:

  • 直接提交的队列:SynchronousQueue队列,它是一个没有容量的队列,前面我有对其进行讲解,当线程池进行入队offer操作的时候,本身是无容量的,所以直接返回false,并没有保存下来,而是直接提交给线程来进行执行,如果没有空余的线程则执行拒绝策略。

  • 有界的任务队列:可以使用ArrayBlockingQueue队列,因为它内部是基于数组来进行实现的,初始化时必须指定容量参数,当使用有界任务队列时,当有任务进行提交时,线程池的线程数量小于corePoolSize则创建新的线程来执行任务,当线程池的线程数量大于corePoolSize的时候,则将提交的任务放入到队列中,当提交的任务塞满队列后,如果线程池的线程数量没有超过maximumPoolSize,则创建新的线程执行任务,如果超过了maximumPoolSize则执行拒绝策略。

  • 无界的任务队列:可以使用LinkedBlockingQueue队列,它内部是基于链表的形式,默认队列的长度是Integer.MAX_VALUE,也可以指定队列的长度,当队列满时进行阻塞操作,当然线程池中采用的是offer方法并不会阻塞线程,当队列满时则返回false,入队成功则则返回true,当使用LinkedBlockingQueue队列时,有任务提交到线程池时,如果线程池的数量小于corePoolSize,线程池会产生新的线程来执行任务,当线程池的线程数量大于corePoolSize时,则将提交的任务放入到队列中,等待执行任务的线程执行完之后进行消费队列中的任务,若后续仍有新的任务提交,而没有空闲的线程时,它会不断往队列中入队提交的任务,直到资源耗尽。

  • 优先任务队列:t有限任务队列是带有执行优先级的队列,他可以使用PriorityBlockingQueue队列,可以控制任务的执行先后顺序,它是一个无界队列,该队列可以根据任务自身的优先级顺序先后执行,在确保性能的同时,也能有很好的质量保证。

上面讲解了关于线程池内部都是通过ThreadPoolExecutor来进行实现的,那么下面我以一个例子来进行源码分析:

public class ThreadPoolDemo1 {

    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(5,
                10,
                60L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(5), new CustomThreadFactory());
        for (int i = 0; i < 15; i++) {
            executorService.execute(() -> {
                try {
                    Thread.sleep(50000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("由线程:" + Thread.currentThread().getName() + "执行任务完成");
            });
        }
    }
}

上面定义了一个线程池,线程池初始化的corePoolSize为5,也就是线程池中线程的数量为5,最大线程maximumThreadPoolSize为10,空余的线程存活的时间是60s,使用LinkedBlockingQueue来作为阻塞队列,这里还发现我自定义了ThreadFactory线程池工厂,这里我真是针对线程创建的时候输出线程池的名称,源码如下所示:

/**
 * 自定义的线程池构造工厂
 */
public class CustomThreadFactory 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;

    public CustomThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                poolNumber.getAndIncrement() +
                "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        String name = namePrefix + threadNumber.getAndIncrement();
        Thread t = new Thread(group, r,
                name,
                0);
        System.out.println("线程池创建,线程名称为:" + name);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }

}

代码和DefaultThreadFactory一样,只是在newThread新建线程的动作的时候输出了线程池的名称,方便查看线程创建的时机,上面main方法中提交了15个任务,调用了execute方法来进行提交任务,在分析execute方法之前我们先了解一下线程的状态:

//假设Integer类型是32位的二进制表示。
//高3位代表线程池的状态,低29位代表的是线程池的数量
//默认是RUNNING状态,线程池的数量为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程个数位数,表示的Integer中除去最高的3位之后剩下的位数表示线程池的个数
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的线程的最大数量
//这里举例是32为机器,表示为00011111111111111111111111111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//线程池的状态
// runState is stored in the high-order bits
//11100000000000000000000000000000
//接受新任务并且处理阻塞队列里面任务
private static final int RUNNING    = -1 << COUNT_BITS;
//000000000
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值