我是靠谱客的博主 儒雅火车,最近开发中收集的这篇文章主要介绍【HBZ分享】java的线程池从创建 到 运行的原理一. 线程池参数含义二. 线程从启动 到 应用的流程第一步:创建队列LinkedBlockingDeque(queueCount)第二步:创建线程第三步:向队列中插入任务第四步:如何应用线程池第五步:为什么不推荐使用Executors?第六步:手写一个简单的线程池,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一. 线程池参数含义

  1. corePoolSize: 核心线程数, 即线程池会提前把核心线程给创建出来,比如corePoolSize = 5, 则会创建5个线程,这5个线程一旦被创建,是不会被回收的,会不断的获取队列中的内容,如果有内容则开始处理
  2. maximumPoolSize:最大线程池数, 即整个线程池最多能开启多少个线程,触发条件是,当核心线程池数被占满了,并且缓冲队列也满了,此时会格外创建线程来处理任务,比如maximumPoolSize = 10, corePoolSize = 5, 那么此时会再创建10-5 = 5个线程来处理任务,如果maximumPoolSize也达到了上限。则触发淘汰策略。
  3. workQueue:缓冲队列,线程池获取任务都是从缓冲队列来的,即使第一次启动线程池,任务也是进入缓冲队列,然后核心线程通过while死循环去队列拿值,类型是LinkedBlockingDeque(queueCount),其中queueCount就是指定队列大小。
  4. keepAliveTime:线程存活时间,这个存活时间指的是,超出核心线程数的其他线程存活时间,默认1分钟。切记,存活时间到了并不会回收核心线程池,只会回收超过核心线程池的部分,比如corePoolSize = 5, 但现在开启了8条线程,多出来3条,那么如果在keepAliveTime时间范围内,这3条线程一直处于空闲状态,没有干活,则会回收这3条线程,只保留核心线程池的5条线程。注意是1分钟内这3条持续没有任务的情况,如果有任务则会刷新keepAliveTime时间,重新置回1分钟。
  5. unit:时间单位, 存活时间是时,分,还是秒, 靠这个配置
  6. handler: 淘汰策略,类型:RejectedExecutionHandler, 默认是超过maximumPoolSize时,后来的线程直接丢弃

二. 线程从启动 到 应用的流程

前提:corePoolSize = 5, maximumPoolSize = 10, workQueue = 8, 此时来了30个任务

  1. 启动时,回直接初始化好workQueue的8个空间的队列。
  2. 然后会创建5个空线程,每个线程都是通过死循环来保障持续运行。
  3. 这5个线程都会不断读取workQueue队列获取任务
  4. 当20个任务来的时候,通过execute方法,该方法中通过workQueue.offer(runnable) 把任务加入到缓冲队列
  5. 此时缓冲队列有值了,则之前创建好的核心线程就会读到队列值,然后调用run()方法
  6. 当5个任务到来时,核心线程数已经满了,则6-14个任务会保存在workQueue缓冲队列中。
  7. 当第15-20个任务来的时候,由于corePoolSize 和 workQueue都满了,所以会格外再开5个线程来执行任务,格外的这5个线程是maximumPoolSize - corePoolSize = 5得来的。
  8. 从第21个任务开始,corePoolSize ,maximumPoolSize , workQueue 全满了,则21-30个任务会放弃

第一步:创建队列LinkedBlockingDeque(queueCount)

首先初始化的时候,会创建一个LinkedBlockingDeque(queueCount)类型的队列,参数就是我们指定的队列长度。

第二步:创建线程

然后会创建线程,常见的线程数 = 核心线程数,配置好核心线程数后,初始化的时候,会创建对应核心线程数的线程,这些线程的run()中会有一个while的死循环,在while中不断去读取LinkedBlockingDeque(queueCount)这个队列,如果队列有值,则直接通过poll(),取出队列值,并从队列删除该元素,然后再调用该值的run()方法即可

class MyThread extends Thread{
@Override
public void run() {
// 4. 死循环中不断读取队列,当为false或者队列为空时关闭
while (startThread || blockingDeque.size() > 0){
// 取出队列最开始的元素,并从队列中删除
Runnable poll = blockingDeque.poll();
if(poll != null){
poll.run();
}
}
}
}

第三步:向队列中插入任务

通过execute(Runnable runnable),把创建的线程插入到队列,等待死循环while中执行该线程


public boolean execute(Runnable runnable){
// 3. 向队列插入Runnable类型任务
return blockingDeque.offer(runnable);
}

第四步:如何应用线程池

当我们要使用线程池的时候,可以这样调用, 就加入到线程池了

 myExecuter.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
});

第五步:为什么不推荐使用Executors?

  1. Exxcutors的4个实现类是:newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor, newCachedThreadPool
  2. 这4个实现类的共同特点是都是使用无界队列
  3. 其中newCachedThreadPool, newScheduledThreadPool 这俩的默认maximumPoolSize = Integer.MAX_VALUE, 即不限制最大线程数
  4. 其中newFixedThreadPool, newSingleThreadExecutor这俩的默认workQueue = Integer.MAX_VALUE, 即不限制最大队列数
  5. 所以无论是不限制maximumPoolSize 还是 workQueue 都可能导致内存溢出,所以极不推荐
  6. LinkedBlockingQueue无参构造方法的队列长度默认就是Integer.MAX_VALUE
源码:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}

第六步:手写一个简单的线程池

/**
* 1. 创建队列LinkedBlockingDeque
* 2. 创建指定数量的无限循环线程
* 3. 向队列插入Runnable类型任务
* 4. 死循环中不断读取队列
*/
public class MyExecuter {
/**
* 核心线程数个队列数量
*/
private List<MyThread> myThreadList;
/**
* 队列对象
*/
private BlockingDeque<Runnable> blockingDeque;
/**
* 队列长度限制
*/
private int queueCount;
/**
* 核心线程池数
*/
private int coreThreadCount;
/**
* 最大线程池数
*/
private int maxThreadCount;
private boolean startThread = true;
/**
* 构造方法初始化
* @param coreThreadCount
核心线程池数量
* @param queueCount
队列数量
* @param maxThreadCount
最大线程池数量
*/
public MyExecuter(int coreThreadCount, int queueCount, int maxThreadCount){
this.queueCount = queueCount;
this.coreThreadCount = coreThreadCount;
this.maxThreadCount = maxThreadCount;
// 1. 初始化队列
blockingDeque = new LinkedBlockingDeque<Runnable>(queueCount);
// 2. 初始化线程
myThreadList = new ArrayList<MyThread>(coreThreadCount);
for(int i = 0; i < coreThreadCount; i++){
new MyThread().start();
}
}
class MyThread extends Thread{
@Override
public void run() {
// 4. 死循环中不断读取队列,当为false或者队列为空时关闭
while (startThread || blockingDeque.size() > 0){
// 取出队列最开始的元素,并从队列中删除
Runnable poll = blockingDeque.poll();
if(poll != null){
poll.run();
}
}
}
}
/**
* 向队列中插入Runnable任务
*/
public boolean execute(Runnable runnable){
// 3. 向队列插入Runnable类型任务
return blockingDeque.offer(runnable);
}
public static void main(String[] args) {
MyExecuter myExecuter = new MyExecuter(2, 2, 4);
for(int i = 0; i < 10; i++){
final int ss = i;
myExecuter.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + ss);
}
});
}
}
}

最后

以上就是儒雅火车为你收集整理的【HBZ分享】java的线程池从创建 到 运行的原理一. 线程池参数含义二. 线程从启动 到 应用的流程第一步:创建队列LinkedBlockingDeque(queueCount)第二步:创建线程第三步:向队列中插入任务第四步:如何应用线程池第五步:为什么不推荐使用Executors?第六步:手写一个简单的线程池的全部内容,希望文章能够帮你解决【HBZ分享】java的线程池从创建 到 运行的原理一. 线程池参数含义二. 线程从启动 到 应用的流程第一步:创建队列LinkedBlockingDeque(queueCount)第二步:创建线程第三步:向队列中插入任务第四步:如何应用线程池第五步:为什么不推荐使用Executors?第六步:手写一个简单的线程池所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(93)

评论列表共有 0 条评论

立即
投稿
返回
顶部