如何改变线程池的工作模式

简介: ## 1.线程池默认的工作模式1. 有任务来了才创建核心线程;2. 当核心线程满了之后把任务堆积到工作队列中;3. 当工作队列满了后扩容线程池,一直到线程个数达到 maximumPoolSize 为止;4. 如果队列已满且达到了最大线程后还有任务进来,按照拒绝策略处理;当线程数大于核心线程数时,线程等待 keepAliveTime 后还是没有任务需要处理的话,收缩线程到核心线程数。如果我想改变线程池的工作模式 核心线程数上限,启用非核心线程数,非核心线程数满了,再放到工作队列中该怎么做呢?

## 1.线程池默认的工作模式

  1. 有任务来了才创建核心线程;
  2. 当核心线程满了之后把任务堆积到工作队列中;
  3. 当工作队列满了后扩容线程池,一直到线程个数达到 maximumPoolSize 为止;
  4. 如果队列已满且达到了最大线程后还有任务进来,按照拒绝策略处理;当线程数大于核心线程数时,线程等待 keepAliveTime 后还是没有任务需要处理的话,收缩线程到核心线程数。

如果我想改变线程池的工作模式

核心线程数上限,启用非核心线程数,非核心线程数满了,再放到工作队列中

该怎么做呢?

image-20220427215457165

2.激进的线程池工作模式

2.1 重写队列的 offer 方法

首先创建一个类,继承 LinkedBlockingQueue 重写 offer 方法 直接返回false 什么都不存 放出假消息 我的队列已经满了

真正存队列的另外在写一个方法 ( 见后文 )

public class MyQueue extends LinkedBlockingQueue {


    @Override
    public boolean offer(Object o) {
        System.out.println(Thread.currentThread().getName()+"  :: 尝试进入队列  当前队列元素个数 :"+super.size());
        return false;
    }
}

2.2 自定义线程池并自定义拒绝策略

image-20220427211159336

image-20220427211745058

如果想达到我们想要的效果 , 那么在核心线程数上限,存放线程任务时,报一个假消息,让线程池误认为工作队列已经满了

这时就会创建非核心线程,执行线程任务, 如果非核心线程数也上限了,那么就会执行拒绝策略

我们设想的是,非核心线程数上限了,这时把线程任务才真正的丢到工作队列中去 那么相应的,就应该自定义一个拒绝策略

所以我们需要重写工作队列和拒绝策略,当然最好自己创建线程工厂

image-20220427212327830

package com.sgg.javaerror100.弹性线程池;

import java.util.concurrent.*;
import java.util.stream.IntStream;

/**
 * @author sz
 * @DATE 2022/4/27  16:22
 */
public class MyThreadPool {

    public static MyQueue workQueue = new MyQueue();

    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                2,
                10,
                10,
                TimeUnit.MINUTES,
                workQueue,
                new ThreadFactory() {
                    int core = 0;
                    int nocore = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        String threadName = core < 2 ? "核心线程" : "非核心线程";
                        if ("核心线程" .equals(threadName)) {
                            return new Thread(r, threadName + " ( " + (++core) +" )");
                        }else {
                            return new Thread(r, threadName +" ( " + (++nocore)+" )");
                        }
                    }
                },
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println(Thread.currentThread().getName() + "  :: 触发拒绝策略");
                        workQueue.doOffer(r);
                    }
                }
        );
        IntStream.rangeClosed(1, 13).forEach(i -> {
                    System.out.println("i = " + i);
                    poolExecutor.execute(() -> {
                        System.out.println("poolExecutor.getPoolSize() = " + poolExecutor.getPoolSize());
                        System.out.println(Thread.currentThread().getName() + "  :: 执行 " + i);
                        try {
                            TimeUnit.DAYS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        );

    }


}

代码创建了一个 核心线程数为2,非核心线程数为8,存活时长为10分钟,并且自定义了线程工厂与拒绝策略 的 线程池

往线程池中提交了13个线程任务 而我们的线程总数只有10个 势必会有3个线程任务 触发拒绝策略

触发拒绝策略时,调用了 MyQueue.doOffer 方法,真正将线程任务放入队列中

public class MyQueue extends LinkedBlockingQueue {

    public boolean doOffer(Object o)
    {
        System.out.println(Thread.currentThread().getName()+"  :: 真正进入队列   当前队列元素个数 :"+ (super.size()+1));
        return super.offer(o);
    }

    @Override
    public boolean offer(Object o) {
        System.out.println(Thread.currentThread().getName()+"  :: 尝试进入队列  当前队列元素个数 :"+super.size());
        return false;
    }
}

这样写能不能达到我们想要的效果呢 ? 看代码演示

2.3 代码演示

idea64_ZLDbBM46uD

输出结果如下

i = 1
poolExecutor.getPoolSize() = 1
核心线程 ( 1 )  :: 执行 1
i = 2
poolExecutor.getPoolSize() = 2
核心线程 ( 2 )  :: 执行 2
i = 3
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 3
非核心线程 ( 1 )  :: 执行 3
i = 4
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 4
非核心线程 ( 2 )  :: 执行 4
i = 5
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 5
非核心线程 ( 3 )  :: 执行 5
i = 6
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 6
非核心线程 ( 4 )  :: 执行 6
i = 7
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 7
非核心线程 ( 5 )  :: 执行 7
i = 8
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 8
非核心线程 ( 6 )  :: 执行 8
i = 9
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 9
非核心线程 ( 7 )  :: 执行 9
i = 10
main  :: 尝试进入队列  当前队列元素个数 :0
poolExecutor.getPoolSize() = 10
非核心线程 ( 8 )  :: 执行 10
i = 11
main  :: 尝试进入队列  当前队列元素个数 :0
main  :: 触发拒绝策略
main  :: 真正进入队列   当前队列元素个数 :1
i = 12
main  :: 尝试进入队列  当前队列元素个数 :1
main  :: 触发拒绝策略
main  :: 真正进入队列   当前队列元素个数 :2
i = 13
main  :: 尝试进入队列  当前队列元素个数 :2
main  :: 触发拒绝策略
main  :: 真正进入队列   当前队列元素个数 :3

2.4 结论

由输出结果可知 确实做到了 先创建核心线程执行线程任务,再由非核心线程执行线程任务,最后在放到队列中,也就是说,我们是可以改变线程池的工作模式的

image-20220427213929514

相关文章
|
7月前
|
存储 缓存 Java
9.队列:生产消费模式及线程池的运用
9.队列:生产消费模式及线程池的运用
61 0
|
7月前
|
数据处理
多线程与并发编程【线程对象锁、死锁及解决方案、线程并发协作、生产者与消费者模式】(四)-全面详解(学习总结---从入门到深化)
多线程与并发编程【线程对象锁、死锁及解决方案、线程并发协作、生产者与消费者模式】(四)-全面详解(学习总结---从入门到深化)
72 1
|
4月前
|
NoSQL Redis
Lettuce的特性和内部实现问题之在同步调用模式下,业务线程是如何拿到结果数据的
Lettuce的特性和内部实现问题之在同步调用模式下,业务线程是如何拿到结果数据的
|
4月前
|
NoSQL 关系型数据库 MySQL
简述redis的单线程模式
简述redis的单线程模式
|
5月前
|
Prometheus 监控 数据可视化
通用快照方案问题之Hystrix进行指标监控如何解决
通用快照方案问题之Hystrix进行指标监控如何解决
46 0
|
5月前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
72 0
|
5月前
|
存储 设计模式 监控
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
52 0
|
7月前
|
缓存 NoSQL 中间件
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?epoll、poll和select + Reactor模式
【5月更文挑战第19天】`epoll`、`poll`和`select`是Linux下多路复用IO的三种方式。`select`需要主动调用检查文件描述符,而`epoll`能实现回调,即使不调用`epoll_wait`也能处理就绪事件。`poll`与`select`类似,但支持更多文件描述符。面试时,重点讲解`epoll`的高效性和`Reactor`模式,该模式包括一个分发器和多个处理器,用于处理连接和读写事件。Redis采用单线程模型结合`epoll`的Reactor模式,确保高性能。在Redis 6.0后引入多线程,但基本原理保持不变。
68 2
|
7月前
|
前端开发 网络协议 JavaScript
如何在前端实现WebSocket发送和接收TCP消息(多线程模式)
请确保在你的服务器端实现WebSocket的处理,以便它可以接受和响应前端发送的消息。同时,考虑处理错误情况和关闭连接的情况以提高可靠性。
545 0
|
缓存 编译器 调度
[笔记]Windows核心编程《七》用户模式下的线程同步
[笔记]Windows核心编程《七》用户模式下的线程同步