学习使用线程池,而不是每次用线程的时候手动去创建,然后再进行销毁,浪费系统资源。
对此线程池应运而生,将一些线程进行复用,循环使用,等下一次业务请求时候,提前创建好的线程对其做一些处理。
为了避免系统频繁的创建和销毁线程,我们可以将创建的线程进行复用,提前创建好一定数量的线程,对外直接提供使用,而不是等到想用的时候手动去创建线程,使用完成之后,再去销毁这个线程,尽可能的复用创建的这些线程。
譬如
:数据库中的数据库连接池,tomcat连接池大小等。
线程池
就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
关于线程需要知道的事
为了让我们更好使用和管理线程池,在JDK1.5中 提供了 Executor 框架,处于java.util.concurrent
Executor 下的接口和类继承关系
ExecutorService 接口中的方法
常用方法说明
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
创建线程时候一般使用java.util.concurrent 包中提供的工具类 Executors
创建线程池。
可以看到此类提供的一些方法
常用的有四个方法用来创建不同的线程池
public static ExecutorService newFixedThreadPool() public static ExecutorService newSingleThreadExecutor() public static ExecutorService newCachedThreadPool() public static ScheduledExecutorService newSingleThreadScheduledExecutor() public static ScheduledExecutorService newScheduledThreadPool()
适用于为了满足资源管理的需求,但需要限制当前线程数量的应用场景,使用需负载比较重的服务器。
newFixedThreadPool 使用无界队列,则线程池中线程数超过核心线程数任务会在队列中等待,线程不会超过核心线程,最大线程数设置将无效,存活时间参数也将无效,所以如果不使用shutdown或shutdownNow方法停止线程池添加任务,不会调用决绝策略拒绝任务。
适用于保证顺序的指定各个任务;并且任意时间点,不会存在多线程是活动的应用场景。
核心线程数和最大线程数都是1,如果是使用无界队列,影响和 newFixedThreadPool
一致
是大小无界的线程池,适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。
newCachedThreadPool是一个根据需要创建线程的线程池,核心线程数为0,最大线程数是int最大值,newCachedThreadPool 是使用没有容量的 SynchronousQueue
作为队列的,但最大线程数(int最大值)是无界的。如果提交的任务速度高于线程处理的速度,那么线程池会不断创建线程,直到资源耗尽;
适用于定期执行任务的场景,可以给定延迟之后的运行任务或者定期执行任务,与 Timer
相似但是功能更强大,Time是单个后台线程,
newSingleThreadScheduledExecutor 可以构造多个线程。DelayedWorkQueue是一个无界队列 所以最大线程参数没什么意义。
经过查看源码之后我们可以发现 上面四种创建线程方法调用的还是 ThreadPoolExecutor
在 Executors 内部创建线程池的时候,实际创建的都是一个 ThreadPoolExecutor 对象,只是对 ThreadPoolExecutor 构造方法,进行了默认值的设定。
ThreadPoolExecutor 的构造方法
其中这7个参数意义表示得含义是
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
默认是使用自带的线程工厂方法创建线程
一些构造方法中也可自定义创建线程工厂,一般我们使用默认就可以。
用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
如果放入LinkedBlockingQueue中的任务超过整型的最大数时,抛出RejectedExecutionException。
默认自带的拒绝则侧,直接抛出异常(有界队列情况下),也可以自定义拒绝策略。
首先根据 Executors 创建线程池 Executors.newScheduledThreadPool(10);
但Idea中阿里守约提示以下信息,不建议使用Executors 去创建线程。
原因是因为:这样子创建线程,线程池中线程最大大小默认情况下是int最大取值大小的队列。
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
源码如下
所以我们尽量使用 ThreadPoolExecutor
方法区创建线程池。
创建了一个核心线程是5最大线程是10 等待回收时间是60s 队列长度是100的一个线程池
返回值得任务
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)); ArrayList<String> list = new ArrayList(); for (int i = 0; i < 5; i++) { Future<String> submit = poolExecutor.submit(() -> "---> " + new Random().nextInt(3000)); try { list.add(submit.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } System.out.println("::" + poolExecutor.isTerminating()); poolExecutor.shutdown(); list.forEach(s -> System.out.println(s));
结果如下
---> 2321
---> 2957
---> 1381
---> 132
---> 1307
创建线程基本区别不大,唯一区别在于提交任务是否有返回值