线程池
原理
概述 :
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。
线程池存在的意义:
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系
统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就
会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
线程池的设计思路 :
- 准备一个任务容器
- 一次性启动多个(2个)消费者线程
- 刚开始任务容器是空的,所以线程都在wait
- 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
- 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来
分类
- newCachedThreadPool: 创建一个可缓存线程池,如果线程池长度超过处理所需,可灵活回收空闲线程,若线程数不够,则新建线程
/**
* 可缓存无界线程池测试
* 当线程池中的线程空闲时间超过60s则会自动回收该线程,核心线程数为0
* 当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,
* 可看做是无限大。
*/
@Test
public void cacheThreadPoolTest() {
// 创建可缓存的无界线程池,可以指定线程工厂,也可以不指定线程工厂
ExecutorService executorService = Executors.newCachedThreadPool(new testThreadPoolFactory("cachedThread"));
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
print("cachedThreadPool");
System.out.println(Thread.currentThread().getName());
}
);
}
}
- newFixedThreadPool: 创建一个固定大小的线程池。可控制并发的线程数量,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
/**
* 创建固定线程数量的线程池测试
* 创建一个固定大小的线程池,该方法可指定线程池的固定大小,对于超出的线程会在LinkedBlockingQueue队列中等待
* 核心线程数可以指定,线程空闲时间为0
*/
@Test
public void fixedThreadPoolTest() {
ExecutorService executorService = Executors.newFixedThreadPool(5, new testThreadPoolFactory("fixedThreadPool"));
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
print("fixedThreadPool");
System.out.println(Thread.currentThread().getName());
}
);
}
}
- newSingleThreadExecutor: 创建一个单线程的线程池,即只创建唯一的工作者线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
/**
* 创建只有一个线程的线程池测试
* 该方法无参数,所有任务都保存队列LinkedBlockingQueue中,核心线程数为1,线程空闲时间为0
* 等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行
*/
@Test
public void singleThreadPoolTest() {
// 创建仅有单个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor(new testThreadPoolFactory("singleThreadPool"));
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
print("singleThreadPool");
System.out.println(Thread.currentThread().getName());
}
);
}
}
- newScheduleThreadPool:创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似 Timer
/**
* 创建定时周期执行的线程池测试
*
* schedule(Runnable command, long delay, TimeUnit unit),延迟一定时间后执行Runnable任务;
* schedule(Callable callable, long delay, TimeUnit unit),延迟一定时间后执行Callable任务;
* scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),延迟一定时间后,以间隔period时间的频率周期性地执行任务;
* scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit),与scheduleAtFixedRate()方法很类似,
* 但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,
* 也就是这一些任务系列的触发时间都是可预知的。
* ScheduledExecutorService功能强大,对于定时执行的任务,建议多采用该方法。
*/
@Test
public void scheduleThreadPoolTest() {
// 创建指定核心线程数,但最大线程数是Integer.MAX_VALUE的可定时执行或周期执行任务的线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5, new testThreadPoolFactory("scheduledThread"));
// 定时执行一次的任务,延迟1s后执行
executorService.schedule(new Runnable() {
@Override
public void run() {
print("scheduleThreadPool");
System.out.println(Thread.currentThread().getName() + ", delay 1s");
}
}, 1, TimeUnit.SECONDS);
// 周期性地执行任务,延迟2s后,每3s一次地周期性执行任务
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ", every 3s");
}
}, 2, 3, TimeUnit.SECONDS);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
long start = new Date().getTime();
System.out.println("scheduleWithFixedDelay 开始执行时间:" +
DateFormat.getTimeInstance().format(new Date()));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = new Date().getTime();
System.out.println("scheduleWithFixedDelay执行花费时间=" + (end - start) / 1000 + "m");
System.out.println("scheduleWithFixedDelay执行完成时间:"
+ DateFormat.getTimeInstance().format(new Date()));
System.out.println("======================================");
}
}, 1, 2, TimeUnit.SECONDS);
}
线程池参数
创建线程池对象 :
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
代码实现 :
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo3 {
// 参数一:corePoolSize 核心线程的最大值,不能小于0
// 参数二:maximumPoolSize 最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
// 参数三:keepAliveTime 空闲线程最大存活时间,不能小于0
// 参数四:unit 时间单位
// 参数五:workQueue 任务队列,不能为null
// 参数六:threadFactory 创建线程工厂,不能为null
// 参数七:handler 任务的拒绝策略,不能为nul
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
任务队列
任务队列(BlockingQueue)指存放被提交但尚未被执行的任务的队列。包括以下几种类型:直接提交的、有界的、无界的、优先任务队列。
-
直接提交的任务队列(SynchronousQueue)
1)SynchronousQueue没有容量。
2) 提交的任务不会被真实的保存在队列中,而总是将新任务提交给线程执行。如果没有空闲的线程,则尝试创建新的线程。如果线程数大于最大值maximumPoolSize,则执行拒绝策略。 -
有界的任务队列(ArrayBlockingQueue)
1)创建队列时,指定队列的最大容量。
2)若有新的任务要执行,如果线程池中的线程数小于corePoolSize,则会优先创建新的线程。若大于corePoolSize,则会将新任务加入到等待队列中。
3)若等待队列已满,无法加入。如果总线程数不大于线程数最大值maximumPoolSize,则创建新的线程执行任务。若大于maximumPoolSize,则执行拒绝策略。 -
无界的任务队列(LinkedBlockingQueue)
1)与有界队列相比,除非系统资源耗尽,否则不存在任务入队失败的情况。
2)若有新的任务要执行,如果线程池中的线程数小于corePoolSize,线程池会创建新的线程。若大于corePoolSize,此时又没有空闲的线程资源,则任务直接进入等待队列。
3) 当线程池中的线程数达到corePoolSize后,线程池不会创建新的线程。
4) 若任务创建和处理的速度差异很大,无界队列将保持快速增长,直到耗尽系统内存。
5) 使用无界队列将导致在所有 corePoolSize 线程都忙时,新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize(因此,maximumPoolSize 的值也就无效了)。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 -
优先任务队列(PriorityBlockingQueue)
1)带有执行优先级的队列。是一个特殊的无界队列。
2)ArrayBlockingQueue和LinkedBlockingQueue都是按照先进先出算法来处理任务。而PriorityBlockingQueue可根据任务自身的优先级顺序先后执行(总是确保高优先级的任务先执行)。
拒绝策略
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
默认拒绝策略为: AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常。