线程池
线程池的概念
线程池是一种线程使用模式,它可以在程序启动时创建一定数量的线程,并放在池子里备用。当有任务需要执行的时候,线程池就会从线程池中取出一个线程执行任务 ,而不是每次都要创建一个新的线程。
线程池的优点
1、减少线程创建和销毁的开销:创建和销毁线程需要一定的系统资源和时间开销。
2、提高系统性能:可以避免频繁创建和销毁线程,从而提高系统的响应性和执行效率。
3、限制资源使用:通过设置线程池的大小,可以合理系统并发运行的线程数量,避免资源耗尽。
4、便于线程管理:可以统一管理线程的创建、调度和销毁。
创建线程池的方式:
直接上代码看看具体有多少创建线程池的方式
Java 标准库线程池的创建方式总共有几种?分别使用代码创建并演示一下
工作中使用第 7 种方式创建线程池
class Thread_2553 {
public static void main(String[] args) {
// 1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建一个操作无界队列且固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 3. 创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
// 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
// 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序
Executors.newWorkStealingPool();
// 7. 自定义线程池,工作中使用这种方法创建线程池,参数含义见课堂讲解内容
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,
10,
10000,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
}
我们如果要自动实现一个自定义的线程池 我们得先掌握一下标准库中线程池的有关参数 下面我们就要讲一下这些参数的作用
线程池的参数
核心线程数 和 最大线程数
在标准库中线程池也会分为两类线程:
1、核心线程: corePoolSize 相当于公司中的正式员工
2、非核心线程:maximumPoolSize就是核心线程数+非核心线程数 相当于公司中的实习生 或者临时工。
核心线程数参数的设计只要根据任务的数量和处理时间来设定的
动态扩展:
一个线程池,刚被创建出来的时候,里面就只有核心线程这么多线程,假设我现在把线程池的核心线程数 和最大线程数 设置为 5 和 10 。
那么现在线程池就包含有5个线程 。
线程池会提供一个submit方法,往里面添加任务,每个任务都是一个Runnable对象。如果现在添加的任务比较少,5个线程足够处理 ,那就只有5个线程进行工作。
但是如果现在添加的任务比较多,4个线程处理不过来了(相当于现在有很多任务在外面排队等待执行) 这个时候,线程池就会自动创建出新的线程,来支持更多的任务 ,但是创建出来的线程总数,不能超过最大的线程数。等过一段时间之后,任务没有那么多了 ,线程得空了,会释放掉部分线程(回收)。
最大空闲时间(keepAliveTime)
这个参数就是表示非核心线程 ,允许等待的最大空闲时间
非核心线程,要是在线程池不忙的时候,回收掉 而不是立即回收 如果在这段时间中又有任务需要执行 那就继续执行任务 。
相当于你是实习生 实习了一段时间 可能现在公司的业务不是那么多了 公司也不需要那么多人手 ,就会考虑辞退你 会给你一段时间 提前通知你
下个月你就可以办理离职了 但要是在这一个月内 公司又有很多业务需要人去做 公司就暂时不考虑辞退你了。
线程池的任务队列
线程池会提供submit方法,让其他线程把任务交给线程池。
线程池内部需要一个队列这样的数据结构,把要执行的任务保存起来 。
在后面线程池工作就会去队列中取出任务进行执行
拒绝策列
这个参数是所有参数中最正确的,去面试的时候,面试官问你线程池中的参数 其实就是在考你对这个参数的理解。
拒绝策列其实是一个枚举类型 他有很多种拒绝策列
意思就是如果当前任务队列满了 还要继续执行任务怎么办
第一种方法 :ThreadPoolExecutor.AbortPolicy
直接抛出异常 (相当于直接告诉程序猿,任务处理不过来了 直接不干了 代码直接罢工了)
第二种方法:ThreadPoolExecutor.CallerRunsPolicy
给线程池添加任务 ,线程池直接告诉添加任务的程序说 我这边满了 让添加任务的程序自己执行 ,线程池本身 不管了.
第三种方式 : ThreadPoolExecutor.DiscardOldestPolicy
丢弃掉最老的任务,让新的任务去队列中排队执行
第四种方式 : ThreadPoolExecutor.DiscardPolicy
丢弃最新的任务,还是按原来的节奏来执行
上面就是标准库的自定义线程池的所有参数了
下面我们自己实现一个线程池:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
// 写一个普通的线程池
class MyTreadPool {
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
private int maxPoolSize = 0;
private List<Thread> threadList = new ArrayList<>(); //用链表来存储一下线程
//初始化线程池
public MyTreadPool(int corePoolSize,int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
//创建若干个线程 核心线程
for (int i = 0; i < corePoolSize; i++) {
Thread t = new Thread(() -> {
try {
while (true) {
Runnable runnable = queue.take();
runnable.run();
}
}catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
threadList.add(t);
}
}
//把任务添加到线程池中 submit方法
void submit (Runnable runnable) throws InterruptedException {
//如果现在任务比较长 说明已有的线程不足以处理这些任务 得新建线程
queue.put(runnable);
if (queue.size() >= 500 && threadList.size() < maxPoolSize) {
//创建新的线程
Thread t = new Thread(() -> {
try {
while (true) {
Runnable task = queue.take();
task.run();
}
}catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class Text {
public static void main(String[] args) throws InterruptedException {
//实例化一个线程池对象
MyTreadPool myTreadPool = new MyTreadPool(10,20);
for (int i = 0; i < 10000; i++) {
int id = i;
myTreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello" + id +"," +Thread.currentThread().getName());
}
});
}
}
}
好了 本篇内容就到这 感谢大家的浏览 和点赞