Java多线程学习笔记(七)--线程池

什么是线程池

为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用,线程池是来管理线程的,线程池中总有那么几个活跃线程,当使用线程时可以从池子中随便拿一个空闲线程,当完成工作时,不用立即关闭线程,而是将线程还给线程池,方便下一个要使用线程的任务,有了线程池后,创建线程变成是从线程池中拿线程,销毁线程时变成是将线程还给线程池继续管理。
这种场景非常像在开发中连接数据库,有一个数据库连接池,需要创建连接时是从连接池中拿一个连接,释放时是将连接还给连接池

解决了什么问题

1.多线程解决了什么问题:之前的电脑是单核的,cpu的资源在多个线程间来回切换,现在发展的很快,电脑大多都是多核的,多线程可以考虑并行的问题,可以有效的利用cpu的资源,提高资源利用率和系统运行效率
2.线程池解决了什么问题:创建和销毁线程时需要消耗资源,创建出来的线程是占用内存的,线程池是来管理线程的,那么问题来了,什么时候使用线程池呢?是需要使用多线程解决问题的时候就使用线程池吗?答案是否定的,因为刚才说过了,创建出来的线程是占用空间的,因此线程池的使用场景是在需要大量线程的情况下,我们使用线程池管理线程,相当于把线程池的创建和销毁均分给了多个任务,在未使用线程池的时候是每个任务执行时都需要去创建和销毁线程,因此,线程池在资源耗费较大的时候,可以有效的避免频繁的创建和销毁线程,从而节省资源,线程本身也会占用内存空间,如果处理不当可能会导致Out of Memory异常,因此选择正确的等待队列是必要的,具体的会在下边详细讲解。

Executor框架结构图


本图来自网上
上图来自于网络
1.Executor是该框架结构图中的最基础的接口,其中定义了线程池execute方法
2.ExecutorService也是一个接口,继承自Executor,定义了线程池的提交及停止,及其他一些判断线程池状态的方法
3.ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务
4.ScheduledExecutorService提供定时执行功能
5.还有一个是Executors,在本图中没有写出,Executors充当一个工厂角色,通过它可以取得一个拥有特定功能的线程池,目前提供的有四种线程池

核心线程池的内部实现


 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

以上三种线程池的实现都是对ThreadPoolExecutor进行了封装,ThreadPoolExecutor这个类为什么如此的功能强大?它的最重要的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

各参数说明:
1.corePoolSize:核心线程数量,指定了线程池中的线程数量,当使用Executors创建了一种线程池时,线程池中是没有活跃的线程的,当提交一个任务时,如果当前线程池中活跃线程数量小于核心线程数量,则会创建一个线程,这个线程就是活跃线程,当线程池中的线程数量达到核心线程数量,再来一个任务需要线程时,这个任务会被放到队列中,等待线程空闲

2.maximumPoolSize:指定了线程池中最大允许的线程数量,当任务来了,队列中已经满了,此时如果线程池中的线程数量没有超过maximumPoolSize的值,则继续创建线程执行任务
3.keepAliveTime:当线程池中的线程数量超过corePoolSize时,其他被创建出来的线程(不是核心线程的线程)的空闲时的存活时间
4.unit:keepAliveTime的单位,包括:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

5.workQueue:任务队列,被提交单尚未被执行的任务,队列类型如下:

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。该类型的构造函数必须带一个容量参数,表示该队列的最大容量。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。提交的任务不会被真是的保存,而总是将新任务提交给线程执行。
  • LinkedBlockingQueue,无解队列,使用时,除非系统资源耗尽,否则,任务是不存在入队失败的情况,
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。总能确保优先级高的任务优先执行

    无界队列和有界队列各有优缺点,无界队列在任务无限多时,无界队列膨胀,耗尽资源,有界队列,可能会造成部分任务被拒绝
    6.threadFactory:线程工厂,用于创建线程
    7.handler:拒绝策略,当任务太多,队列满了,线程池中的线程数量达到了最大的线程数量后,就需要执行拒绝策略,JDK提供了四种拒绝策略,如下:

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作

  • CallerRunsPolicy策略:直接在execute方法的调用线程中运行被拒绝的任务,如果执行程序已经关闭,则会丢弃该任务
  • DiscardOledestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被调度执行的任务,并尝试再次提交当前任务
  • DiscardPolicy策略:直接丢弃任务,不予任何处理


任务执行过程


由以上的ThreadPoolExecutor的参数详解了解了各参数都是干什么的,这几个参数是如何协同工作,完成任务执行呢?
源码解析:

public void execute(Runnable command) {
        // 命令为null,抛出异常
        if (command == null)
            throw new NullPointerException();
       /*
        * 进行下面三步
        *
        * 1. 如果运行的线程小于corePoolSize,则尝试使用用户定义的Runnalbe对象创建一个新的线程
        *     调用addWorker函数会原子性的检查runState和workCount,通过返回false来防止在不应
        *     该添加线程时添加了线程
        * 2. 如果一个任务能够成功入队列,在添加一个线城时仍需要进行双重检查(因为在前一次检查后
        *     该线程死亡了),或者当进入到此方法时,线程池已经shutdown了,所以需要再次检查状态,
        *    若有必要,当停止时还需要回滚入队列操作,或者当线程池没有线程时需要创建一个新线程
        * 3. 如果无法入队列,那么需要增加一个新线程,如果此操作失败,那么就意味着线程池已经shut
        *     down或者已经饱和了,所以拒绝任务
        */

        int c = ctl.get();// 获取线程池控制状态
        if (workerCountOf(c) < corePoolSize) {// worker数量小于corePoolSize
            if (addWorker(command, true)) // 添加worker
                return;// 成功则返回
            c = ctl.get();// 不成功则再次获取线程池控制状态
        }
        if (isRunning(c) && workQueue.offer(command)) {// 线程池处于RUNNING状态,将命令(用户自定义的Runnable对象)添加进workQueue队列
        // 再次检查,获取线程池控制状态
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command)) // 线程池不处于RUNNING状态,将命令从workQueue队列中移除
                reject(command); // 拒绝执行命令
            else if (workerCountOf(recheck) == 0) // worker数量等于0
                addWorker(null, false);// 添加worker
        }
        else if (!addWorker(command, false))// 添加worker失败
            reject(command);// 拒绝执行命令
    }

这里写图片描述

4种线程池


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

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

该方法返回一个固定大小的线程池,看这种类型的线程池强两个参数corePoolSize和maximumPoolSize都是nThreads,因此它是固定大小的线程池,刚创建时,线程池中是没有活跃线程的,当创建的线程数量达到corePoolSize后,线程池中的线程数量是一直不变的
2.newSingleThreadExecutor()

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

该方法返回一个只有一个线程的线程池,若多余一个任务被提交到该线程池,任务会被保存在一个队列中,待线程空闲,按先进后出的顺序执行队列中的任务
3.newCachedThreadPool()

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

该方法返回一个可根据实际情况调整线程数量的线程池,线程池的线程数量不确定,若空闲线程可复用,则会优先使用可复用的线程
4.newSingleThreadScheduledExecutor()

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

该方法返回一个ScheduledExecutorService对象,线程池大小为1

推荐博客

猜你喜欢

转载自blog.csdn.net/zh15732621679/article/details/80376365
今日推荐