JAVA线程池

一、线程池的概念

1.1线程池是什么

官方解释:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

个人理解:顾名思义,线程池就是装线程的容器呗,只要我们设定好参数,这个池子可以按照我们的要求,帮我们自动创建线程、管理线程,我们不必再自己new一个thread,也不用再花精力去考虑每个线程的生命周期。

1.2 为什么使用线程池

创建一个线程的开销是比较大的,因为这涉及到和操作系统的交互,如果我们创建的很多个线程都是为了很生命周期短暂、相对简单的这种任务,完成任务后就把线程销毁,就显得十分浪费资源。另一方面,自己创建的线程如果太多,管理起来将会十分繁琐。为了应对这种问题,我们就可以使用线程池。线程池的使用也是比较简单,配置好参数之后,我们向线程池提交任务(runnable),线程池就会按照自己的策略自动开始处理任务。

二、JAVA中的线程池

在JAVA中,我们可以使用ThreadPoolExecutor来创建线程池,这个类的构造函数如下:

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几个重要参数和概念的解释

上文中提到了JAVA中可以使用ThreadPoolExecutor来创建线程池,从构造方法中,可以看到有7个参数,接下来对其一一解释。

2.1.1 corePoolSize核心线程数

核心线程,指的是在一般情况下,一直存在、不会被销毁的线程,也就是说即使任务已经完成,闲置着,这个线程也不会被销毁。

与核心线程相对的,就是普通线程,普通线程是指完成任务后,闲置时间超过一定时长后,就会被回收的这种线程。

线程池刚创建时,没有任务需要处理,也没有线程。当任务陆续提交到线程池,就会开始创建线程,刚开始只要当前线程数不够用,就会创建线程,但是当线程池中的线程数目达到 corePoolSize后,如果依旧有新来的任务,就不会再立刻创建线程而是先被添加到缓存队列中。所以corePoolSize这个参数,就是指核心线程的数量,例如我们设置核心线程数为10,那么就意味着线程池中刚开始只要有需要就会创建最多10个线程,而且在完成任务之后,这10个线程依旧会“随时待命”,它们会一直等待新任务,不会被销毁。

2.1.2 maximumPoolSize最大线程数量

这个参数的意思是线程池中能够容纳同时执行的最大线程数。如果线程池中的线程数已经达到corePoolSize,并且缓冲队列也满了,这时就要看线程池中当前已经存在的线程数是否大于 maximumPoolSize,如果小于maximumPoolSize ,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来应对这个任务。 比如,如果我们设置了maximumPoolSize=50,如果此时有超多任务需要线程来执行,但现在缓冲队列已经满了了,而且线程池中已经创建了50个线程都在处理这些任务,实在没有空闲线程去处理多余任务,但即使在种情况下,线程池也不会再去创建更多线程了,因为我们限制了maximumPoolSize=50,没办法,线程池只能启用拒绝策略,至于拒绝哪个任务,怎么拒绝,后面在线程池的拒绝策略中会详细介绍。另外,maximumPoolSize此值必须大于等于1。

2.1.3 keepAliveTime线程最大空闲时间

上文说到,普通线程在完成任务之后会闲置,闲置的一段时间后,如果依旧没有任务,就会销毁,这里的“一段时间”,就是指的keepAliveTime。还有一个参数TimeUnit,是用来指定时间单位,比如我们设置keepAliveTime=60,TimeUnit=TimeUnit.SECONDS,就是指线程最大空闲时间为60秒。时间单位除了SECONDS之外,还有NANOSECONDS(纳秒),MICROSECONDS(微妙),MILLISECONDS(毫秒),MINUTES(分钟)、HOURS(小时)、DAYS(天)等。

2.1.4 BlockingQueue<Runnable>任务缓冲队列

上文中我们说道,线程池创建之初,如有多个任务,线程池会创建新的线程去处理,但如果创建的线程已经达到了corePoolSize,依然不能满足需求,就会把多余的任务暂时放到一个缓冲队列(也就是BlockingQueue<Runnable>)中,等待空闲线程来处理。我们可以使用以下几种阻塞队列充当任务缓冲队列:

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

LinkedBlockingQueue :由链表结构组成的有界阻塞队列(常用)

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

DelayQueue: 使用优先级队列实现的无界阻塞队列

SynchronousQueue: 不存储元素的阻塞队列(常用)

LinkedTransferQueue: 由链表结构组成的无界阻塞队列

LinkedBlockingDeque: 由链表结构组成的双向阻塞队列

2.1.5 threadFactory线程工厂

线程工厂,用来为线程池创建线程,若我们不指定线程工厂,线程池内部会调用Executors.defaultThreadFactory()使用默认的线程工厂,该工厂创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一些操作。

2.1.6 RejectedExecutionHandler拒绝策略

上文说到,如果提交的任务过多,缓冲队列都满了。而且线程池中的正在执行任务的线程已经达到最大maximumPoolSize,实在无法再处理这么多任务了,线程池就会执行拒绝策略,有如下几种拒绝策略可供选择:

DiscardOldestPolicy :抛弃最早进来的任务

AbortPolicy:直接丢弃任务,并抛出RejectedExecutionException异常

CallerRunsPolicy:哪个线程传过来的这个任务,就丢给哪个线程自己去处理

DiscardPolicy:直接丢弃任务,但是不抛出异常

2.2 线程池工作原理

其实在2.1节介绍线程池各个参数的含义时,就已经大致描述了线程池的工作流程,但是比较零散,不够系统,接下来,我们用流程图的方式来描述线程池的工作原理:

三、线程池的使用

3.1 execute()和submit()

我们在创建好线程池后,使用execute()和submit()都可以向线程池中添加任务来执行,但这两个方法也是有区别的。通过源码,我们可以看出,execute方法需要传入runnable,并且没有返回值。

而submit方法不仅可以接受Runnable,还能接受Callable,并且能够有Future类型的返回值。

概括一下,execute()和submit()方法有以下区别:

  1. 接受的参数不同
  2.  submit()有返回值,能够获取到执行的结果等信息,而execute()没有返回值;
  3. submit()方便异常处理,如果在任务中里会抛出异常,我们又希望外面的调用者能够感知这些异常并做出及时的处理,那么就需要用到submit(),通过捕获Future.get()抛出的异常来进行处理。

3.2 使用线程池处理任务

本节我们来实际运用一下线程池。我们先新建500个任务,然后提交到线程池中,观察输出结果。相关源码如下:

package com.example.threadtest.threadpool;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    private static ArrayList<Runnable> runnableArrayList = new ArrayList<>();
    private static AtomicInteger index = new AtomicInteger(0);

    public static void main(String[] args) {
        createRunnable();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(20), new ThreadFactory() {
            @Override
            public Thread newThread(@NotNull Runnable runnable) {
                return new Thread(runnable);
            }
        }, new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
                threadPoolExecutor.shutdown();
                throw new RejectedExecutionException("Task " + runnable.toString() + " rejected from " + "异常抛出时间:" + threadPoolExecutor.toString() + System.currentTimeMillis());
            }
        });
        for (int i = 0; i < runnableArrayList.size(); i++) {
            //使用execute()执行任务
//            threadPoolExecutor.execute(runnableArrayList.get(i));
            //使用submit()执行任务
            Future<?> future = threadPoolExecutor.submit(runnableArrayList.get(i));
        }

    }

    private static void createRunnable() {
        for (int i = 0; i < 500; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + "处理了任务" + index.incrementAndGet() + ",时间:" + System.currentTimeMillis());
                    try {
                        Thread.sleep(5000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            runnableArrayList.add(runnable);
        }
    }

}

9:25:58: Executing task 'Main.main()'...

Executing tasks: [Main.main()] in project D:\project\wuhan\MyApplication2

> Task :ThreadTest:compileKotlin

> Task :ThreadTest:compileJava

> Task :ThreadTest:processResources NO-SOURCE

> Task :ThreadTest:classes

> Task :ThreadTest:Main.main()

Thread-0处理了任务1,时间:1626830758968

Thread-8处理了任务5,时间:1626830758968

Thread-7处理了任务4,时间:1626830758968

Thread-6处理了任务3,时间:1626830758968

Thread-3处理了任务2,时间:1626830758968

Thread-9处理了任务9,时间:1626830758969

Thread-1处理了任务10,时间:1626830758969

Thread-4处理了任务8,时间:1626830758969

Thread-10处理了任务11,时间:1626830758969

Thread-2处理了任务7,时间:1626830758969

Thread-5处理了任务6,时间:1626830758968

Thread-14处理了任务12,时间:1626830758969

Thread-11处理了任务13,时间:1626830758969

Thread-13处理了任务14,时间:1626830758969

Thread-12处理了任务15,时间:1626830758969

Thread-19处理了任务16,时间:1626830758969

Thread-16处理了任务17,时间:1626830758969

Thread-17处理了任务18,时间:1626830758969

Thread-18处理了任务19,时间:1626830758969

Thread-15处理了任务20,时间:1626830758969

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7f31245a rejected from 异常抛出时间:java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Shutting down, pool size = 20, active threads = 20, queued tasks = 20, completed tasks = 0]1626830758969

at com.example.threadtest.threadpool.Main$2.rejectedExecution(Main.java:40)

at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)

at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)

at com.example.threadtest.threadpool.Main.main(Main.java:47)

Thread-16处理了任务21,时间:1626830763976

Thread-8处理了任务22,时间:1626830763976

Thread-19处理了任务23,时间:1626830763978

Thread-1处理了任务24,时间:1626830763978

Thread-9处理了任务26,时间:1626830763978

Thread-11处理了任务27,时间:1626830763978

Thread-13处理了任务25,时间:1626830763978

Thread-18处理了任务34,时间:1626830763978

Thread-4处理了任务33,时间:1626830763978

Thread-10处理了任务38,时间:1626830763979

Thread-0处理了任务32,时间:1626830763978

Thread-7处理了任务31,时间:1626830763978

Thread-6处理了任务30,时间:1626830763978

Thread-2处理了任务29,时间:1626830763978

Thread-5处理了任务28,时间:1626830763978

Thread-15处理了任务40,时间:1626830763979

Thread-3处理了任务39,时间:1626830763979

Thread-14处理了任务37,时间:1626830763979

Thread-17处理了任务36,时间:1626830763979

Thread-12处理了任务35,时间:1626830763978

> Task :ThreadTest:Main.main() FAILED

通过log,我们看到在处理完任务20时,由于当前没有空闲线程能够处理新任务,也不能再新建线程,并且任务队列已经满了,所以再有新任务提交进来,就直接抛出了异常,线程池不再接受新的任务,但是在抛异常之前就已经添加进来的任务,还是会处理完。当然,还有很多任务没有处理完,线程池抛出了异常,也从一定程度上反映出我们线程池参数配置的不太合理。


总结

本文主要介绍了JAVA线程池的相关知识,如有错误还请各位大神指出,同时也欢迎各位大佬来讨论和交流技术,本人邮箱hbutys@vip.qq.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值