目录
线程池概念
线程池:把一个或多个线程通过统一的方式进行调度和重复使用的技术。相当于往一个池里放入一些线程,要使用了,就拿出来,用完了就还回去。
线程池优点
线程池相较于之前创建/销毁线程(操作系统实现),变成了取出来使用,不用了就还回去(用户代码实现),这样的方法更为高效,降低了资源消耗。
由于在操作系统中许多情况是位置的,而有了线程池就会让我们掌握主动权,从而就提高了线程的可管理性和任务执行的响应速度。
使用线程池
有关并发编程的使用,大多都在java.util.concurrent这个包下面,简称 juc 。
Executor接口:
所有有关线程池使用的类都是实现了该接口。
ThreadPoolExecutor类:
使用这个类,就可以使用线程池了。
构造方法
这个类的构造方法有四个,其参数较为复杂。
对于这四个构造方法,可以看到第四个构造方法的参数最多,下面详细介绍一下参数的意义。
参数 | 意义 |
int corePoolSize | 核心线程数量 核心线程是线程池当中一直保存的线程。 |
int maximumPoolSize | 最大线程数量 线程池中的线程 = 核心线程 + 临时线程 该参数就是二者之和 如果当前任务比较多,核心线程不够用,那么就可以多创建一些临时线程干活;如果当前任务少,并且持续了一段时间了,此时就可以销毁一些临时线程。 这个最大数量没有确定值。但是根据具体场景大致可以分为两类: ①CPU密集型 此时每个线程执行的任务都是很吃CPU的,此时就算搞再多的线程有没有用。设置成CPU内核数即可。 ②IO密集型 此时每个线程执行的任务都是在等待IO(读写硬盘、读写网卡等),此时线程处于阻塞状态,不参与CPU的调度,这样就可以尽可能的多搞一些线程。 上面两种情况都是极端情况,实际上我们可以根据这两种情况作为参考,试一些数来选出最优情况。 |
long keepAliveTime | 临时线程最大空闲时间 当临时线程空闲时间超过这个值时,此时就会被销毁。 |
TimeUnit unit | 上述空闲时间的单位 枚举类型 NANOSECONDS : 纳秒 MICROSECONDS :微秒 MILLISECONDS : 毫秒 SECONDS : 秒 MINUTES : 分 HOURS : 小时 DAYS : 天 |
BlockingQueue<Runnable> workQueue | 线程池的任务队列 此处使用阻塞队列,如果有任务就take()成功,没有就阻塞,不用一直take() |
ThreadFactory threadFactory | 创建线程 这是一个接口,new时需要实现 线程池中创建的线程由该它重写方法 Thread newThread(Runnable r); 线程池中的线程都是前台线程。 |
RejectedExecutionHandler handler | 拒绝策略 拒绝策略是当线程池的任务队列满了之后,如果再往里面添加任务会有什么样的行为。标准库提供了四种拒绝策略。下图所示 ①直接抛出异常 ②多出来的任务,谁加的,谁负责执行 ③丢弃最早的任务,给新任务腾地方 ④丢弃最新的任务,保持不变 |
代码演示:
import java.util.concurrent.*;
public class ThreadDemo29 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(8, 16, 20L,
TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(8),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行该任务!");
}
});
}
}
}
可以看出,使用该类实现线程池是较为繁琐的。通常我们不使用该类实现线程池,而是使用一个工厂类。
Executors工厂类:
工厂类:简单的理解为用普通方法代替构造方法。
Executors里的工厂方法把上面的ThreadPoolExecutor类的构造方法加工了一下,使其变得更为简单好用给。
工厂方法
下面四种常用的方法都是静态方法,返回类型为 ExecutorService
方法 | 说明 |
Executors.newFixedThreadPool(int nThreads) | 创建含有 nThreads个 线程的线程池 |
Executors.newCachedThreadPool() | 创建的线程池中的线程数量不定,任务与线程数量成正相关 |
Executors.newScheduledThreadPool(int corePoolSize) | 创建含有 corePoolSize个 核心线程的线程池 类似于之前的定时器,让任务延时执行。扫描线程的任务由线程池自己完成。 |
Executors.newSingleThreadExecuto() | 创建只有一个线程的线程池 |
代码演示
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo27 {
public static void main(String[] args) {
ExecutorService pool1 = Executors.newFixedThreadPool(8);
ExecutorService pool2 = Executors.newCachedThreadPool();
ExecutorService pool3 = Executors.newScheduledThreadPool(2);
ExecutorService pool4 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
pool1.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务!");
}
});
}
for (int i = 0; i < 5; i++) {
pool2.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务!");
}
});
}
for (int i = 0; i < 5; i++) {
pool3.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务!");
}
});
}
for (int i = 0; i < 5; i++) {
pool4.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务!");
}
});
}
}
}
线程池中的常用方法
方法 | 说明 |
Future<?> submit(Runnable task) | 执行提交的任务 返回值 是Future接口 当任务执行期间,我可以干其他事情,当任务结束后,返回值给Future,我想什么时候回来取结果都可以 参数类型 Runnable类型的任务 |
void execute(Runnable command) | 执行提交的任务 返回值 为空 与上面的submit的区别 参数类型 Runnable类型的任务 |
void shutdown() | 启动有序关闭 先提交的任务先被执行,同时不接受任何新任务 不会报错,执行完成后正常退出 |
List<Runnable> shutdownNow() | 尝试立刻停止任务 如果有还在执行的任务,会抛出 java.lang.InterruptedException: sleep interrupted 异常 如果没有,则返回等待执行的任务列表 |
boolean isShutdown() | 判断线程池是否关闭 true --- 关闭 false --- 未关闭 |
boolean isTerminated() | 判断线程池是不是完成所有任务而关闭 true --- 完成所有任务关闭 false --- 为完成所有任务关闭 |
线程池的工作流程
线程池的状态
线程池有五种状态。
RUNNING
线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。
SHUTDOWN
不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。
STOP
不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。
TIDYING
①SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。
②线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。
③线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。
TERMINATED
线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。
简单实现线程池
代码注释中详解。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
// 使用阻塞队列保存任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// 构造方法中先创建几个线程
public MyThreadPool (int n) {
//
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while (true) {
try {
// 一直从阻塞队列中拿任务
// 如果没有任务就自动阻塞
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
// 提交任务给线程池中的阻塞队列
public void submit(Runnable task) {
try {
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadDemo28 {
public static void main(String[] args) {
MyThreadPool myThreadPool = new MyThreadPool(10);
for (int i = 0; i < 10; i++) {
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务!");
}
});
}
}
}
有什么错误评论区指出。希望可以帮到你。