Java多线程案例——线程池及ThreadPoolExecutor类
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
一线程池
1.为什么会有线程池线程池和多线程的区别
为了很好的解决高并发问题提高计算机的运行效率提出了多线程来取代多进程因为一个线程的创创建、销毁和调度比进程更加“轻量”所以线程也被称作“轻量级进程”这就是线程存在的意义
随着并发程度的提高随着我们对于性能要求标准的提高我们发现线程的创建也没有那么“轻量”因为线程的创建销毁和调度都源自于操作系统内核频繁的对线程进行操作开销也会很大所以线程池的概念也随之诞生。
线程池就是在多线程的基础上减少了对线程频繁创建、销毁和调度的操作来降低创建、销毁线程的开销其核心思想事先把需要使用的线程创建好放到“池”中后面需要使用的时候直接从池里获取用完了还给“池”。
主要区别
多线程创建、销毁线程都是交由操作系统内核来完成
线程池事先创建好线程从“池子”里获取还给“池子”都是由用户代码实现不用交给内核操作
主要改进
减少了在多线程环境下操作系统内核频繁创建、销毁线程的开销
2.线程池的创建
在Java标准库中对于线程池的创建主要是通过Executors这个工厂类工厂模式就是使用普通方法来代替构造方法创建对象=相当于是把new操作给隐藏到普通方法后面来实现。
构造方法只介绍常见的
newFixedThreadPool | 创建指定数目线程的线程池 |
newCachedThreadPool | 线程数量是动态变化的任务多了就多创建几个线程 |
newSingleThreadExecutor | 线程池内只有一个线程 |
newScheduledThreadPool | 类似于定时器只是执行扫描任务的时候不是由扫描线程完成而是线程池内的线程完成 |
submit方法
submit方法是为了用来给线程池提交任务传入的参数是Runnable类型的。
线程池代码示例
/**
* 创建一个包含10个线程的线程池去执行1000个任务
* 每个任务被执行的时候打印哪个线程正在执行任务来知道当前被调度的线程
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo3 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务");
}
});
}
}
}
发现此时线程执行任务的顺序是随机的因为每个线程执行完一个任务之后再立即取下一个任务由于每个线程执行任务的时间不同因此每个线程并不是按照一定的顺序来执行。而且我们发信这里的进程并没有结束这点类似于之前说的Timer计时器类所创建的线程都是前台线程会阻止进程的结束。
二模拟实现线程池
简单的线程池需要实现两个功能
阻塞队列保存任务
若干个工作线程
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
//使用阻塞队列保存任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
//此处n表示线程数量
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 runnable) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadDemo6 {
public static void main(String[] args) {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int n = i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"正在执行任务");
}
});
}
}
}
通过编译可以执行效果同Java标注库提供的submit方法一样。
三ThreadPoolExecutor
上面构造方法中提到的那些线程池本质上都是通过包装ThreadPoolExecutor类来实现的只是ThreadPoolExecutor这个线程池用起来更麻烦所以Java标准库给我们提供了工厂类因为其参数过多使其使用起来变得复杂但是在面试中ThreadPoolExecutor类的参数的含义考的非常多所以我们需要做一个简单的了解。
ThreadPoolExecutor的构造方法
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecytionHandler handler) |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecytionHandler handler) |
这里我们只讲解第四种构造方法因为第四种的构造方法最为复杂包含了所有参数。
corePoolSize核心线程数
一般指最少包含的线程数量类比公司的正式员工
maximumPoolSize最大线程数
线程池所能容纳的最大线程数当活跃线程数达到该数值后后续的新任务将会阻塞类比公司的实习生正式员工+实习生 = 最大线程数
keepAliveTime线程闲置超时时长
如果超过该时长非核心线程就会被回收描述了实习生可以偷懒的最大时间
unit时间单位
指定 keepAliveTime 参数的时间单位。常用的有TimeUnit.MILLISECONDS毫秒、TimeUnit.SECONDS秒、TimeUnit.MINUTES分
BlockingQueue<Runnable> workQueue线程池的任务队列
任务队列通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中采用阻塞队列实现
ThreadFactory threadFactory线程工厂
用于指定为线程池创建新线程的方式
RejectedExecytionHandler handler拒绝策略
描述线程的“拒绝策略”也是一个特殊的对象描述了当线程朝任务队列满了如果继续添加任务会有啥样的行为。
拒绝策略