1.什么是线程池
线程池一个容纳了多个线程的容器,其中的线程可以反复的使用。省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源
2.线程池优点
在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地使用线程池,线程池的优势很明显,如下:
1.降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
2.提高系统响应速度,当有任务到达时,无序等待新线程的创建便能立即执行;
3.方便线程并发的管控,线程若是无线制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而堵塞系统。
4.更强大功能,线程池提供了定时,定期以及可控线程等功能的线程池,使用方便简单;
3.线程池创建
在 Java 语言中,
并发
编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:通过 ThreadPoolExecutor 手动创建线程池。
通过 Executors 执行器自动创建线程池。
而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:
3.1固定大小的线程池:Executors.newFixedThreadPool
创建一个固定大小的线程池,可控制并发线程数。使用 FixedThreadPool 创建 2 个固定大小的线程池,具体实现代码如下:
package com.qfedu.c_object;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo3 {
public static void main(String[] args) {
fixedThreadPool();
}
public static void fixedThreadPool() {
// 创建 2 个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
}
};
// 线程池执行任务(一次添加 4 个任务)
// 执行任务的方法有两种:submit 和 execute
threadPool.submit(runnable); // 执行方式 1:submit
threadPool.execute(runnable); // 执行方式 2:execute
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}
3.2可以缓存的线程池:Executors.newCachedThreadPool()
创建一个可缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。CachedThreadPool 使用示例如下:
线程数超过了任务数。有四个线程 但是任务有两个 只需要两个线程就可以了,其他两个线程回收
线程数低于任务数,新建线程!!!自动新建
package com.qfedu.c_object;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
cachedThreadPool();
}
public static void cachedThreadPool() {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 执行任务
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
});
}
}
}
线程池创建了 10 个线程来执行相应的任务。
使用场景
CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。
3.3创建单个线程的线程池:Executors.newSingleThreadExecutor()
创建单个线程的线程池,它可以保证先进先出(队列)的执行顺序。SingleThreadExecutor 使用示例如下:
package com.qfedu.c_object;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
singleThreadExecutor();
}
public static void singleThreadExecutor() {
// 创建线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + ":任务被执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
});
}
}
}
单个线程的线程池有什么意义?
单个线程的线程池相比于线程来说,它的优点有以下 2 个:
- 可以复用线程:即使是单个线程池,也可以复用线程。
- 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。
3.4创建一个可以执行延迟任务的线程池:Executors.newScheduledThreadPool()
创建一个可以执行延迟任务的线程池。使用示例如下:
package com.wyx.g_threadpool;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
System.out.println("添加任务时间" + new Date());
//schedule(command, delay, unit)的三个参数
//command:要执行的任务,也就是run方法中的代码
//delay:从现在开始延迟执行的时间
//unit:延迟参数的时间单位
threadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了任务," + "任务被执行,被执行时间为" + new Date());
}
}, 3, TimeUnit.SECONDS);
}
}
上述结果可以看出,任务在 3 秒之后被执行了,实现了延迟 3s 再执行任务
3.5创建一个单线程的可以执行延迟任务的线程池:Executors.newSingleThreadScheduledExecutor()
创建一个单线程的可以执行延迟任务的线程池,此线程池可以看作是 ScheduledThreadPool 的单线程池版本。它的使用示例如下:
ublic static void SingleThreadScheduledExecutor() {
// 创建线程池
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 添加定时执行任务(2s 后执行)
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(() -> {
System.out.println("任务被执行,时间:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 2, TimeUnit.SECONDS);
}
上述结果可以看出,任务在 2 秒之后被执行了
3.6创建一个抢占式执行的线程池:Executors.newWorkStealingPool()
创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。newWorkStealingPool 使用示例如下:
package com.qfedu.c_object;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
workStealingPool();
}
public static void workStealingPool() {
// 创建线程池
ExecutorService threadPool = Executors.newWorkStealingPool();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
}
});
}
// 确保任务执行完成
while (!threadPool.isTerminated()) {
}
}
}
从上述结果可以看出,任务的执行顺序是不确定的,因为它是抢占式执行的。
以上都不用!!!
3.7原始线程池:new ThreadPoolExecutor【以后开发要用的】
ThreadPoolExecutor 是最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。这7个参数面试问!
优势:和其他相比,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,所以在阿里巴巴《java开发手册》是这样规定的,强制要求线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,避免资源耗尽的风险。
ThreadPoolExecutor的七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- int corePoolSize:线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
- int maximumPoolSize:线程池最大线程数量
一个任务被提交到线程池后,首先会缓存到工作队列(后面会介绍)中,如果工作队列满了,则会创建一个新线程,然后从工作队列中的取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize来指定。
- long keepAliveTime:空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。
- TimeUnit unit:计量单位
keepAliveTime的计量单位
- BlockingQueue<Runnable> workQueue:工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene(开发时候使用)
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。不会新建,效率高。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
- ThreadFactory threadFactory:线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
- RejectedExecutionHandler handler:拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
ThreadPoolExecutor 使用示例如下:
package com.wyx.h_threadpoolexecutor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) {
//创建线程池对象
//创建一个核心线程为5,最大线程为10,1秒后会释放掉空闲线程,工作队列为10的线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
//new ArrayBlockingQueue<>(10)工作队列分析
//如果执行任务小于等于5,不会创建新线程,队列中不会有任务排队
//如果执行任务小大于5小于等于15,不会创建新线程,队列中会有10个任务在排队
//如果执行任务大于15小于等于20,会创建新线程,创建的新线程不会超过最大线程数,队列中会有任务排队。
//如果执行任务大于20,这时创建的线程数已为最大,工作队列也会满,但还是装不下这些任务,此时会执行拒绝策略。
//总结:只要队列不满,都不会创建新的线程,始终都是核心线程在工作,一旦队列满了,就会创建新的线程,
//但是创建的新的线程数不会超过最大线程数,如果说创建的新的线程数已达最大,队列也满了,此时会执行拒绝策略。
//执行任务,循环十次,十个任务
for (int i = 0; i < 10; i++) {
int index = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了" + index + "号任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
}
package com.wyx.h_threadpoolexecutor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo2 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
//new LinkedBlockingDeque<>(10),始终不会创建新的线程,
//当执行任务超过20时候,会执行拒绝策略。
//执行任务,创建了10个任务
for (int i = 0; i < 10; i++) {
int index = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了" + index + "号任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
}
4.线程池关闭
4.1shutdown关闭线程池
线程池的状态变成SHUTDOWN状态,此时不能再往线程池中添加新的任务,否则会抛出RejectedExecutionException异常。
线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
4.2shutdownNow关闭线程池并中断任务
线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。
终止等待执行的线程,并返回它们的列表;
试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt(),但是大家知道,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
package com.wyx.h_threadpoolexecutor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo2 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
//new LinkedBlockingDeque<>(10),始终不会创建新的线程,
//当执行任务超过20时候,会执行拒绝策略。
//执行任务,创建了10个任务
for (int i = 0; i < 10; i++) {
int index = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了" + index + "号任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
//关闭线程池
threadPoolExecutor.shutdown();
}
}