如何动态调试线程池?
这是有小伙伴最近在面深信服的时候遇到的一个问题,感觉比较有意思,松哥和大伙来聊一聊。
如何动态调试线程池?
面试官表示设置线程池核心线程数是一个非常具有挑战性的事情,问有无办法能够动态的设置线程池核心数,并观察其执行效果?
这个问题的难点在于它涉及到的技术点不是特别常用,该小伙伴面试的技术团队刚好是做运维工具的,做一些监控软件,所以刚好就问到这里。
那么松哥和大家简单聊一聊这个话题。
其实这里主要是涉及到 Java 里边一个比较古老的工具,JMX。
一 什么是 JMX
JMX(Java Management Extensions)是 Java 平台的一部分,它提供了一种管理和监控 Java 应用程序的标准方法。JMX 允许你监控和管理系统资源、应用程序和服务,以及获取关于这些实体的运行时信息。
简单来说,就是通过 JMX 可以动态查看对象的运行信息,并且可以动态修改对象属性。
JMX 架构如下图:
分析这张图我们可以发现,JMX 底层是由很多不同的 MBeans 组成的,MBeans 是 JMX 的核心,它们是实现了特定接口的 Java 对象,用于表示可以被监控和管理的资源。MBeans 可以分为四种不同的类型,分别是:
- Standard MBeans
- Dynamic MBeans
- Open MBeans
- Model MBeans
这些 MBeans 的作用就是获取对象的信息,或者是修改对象信息,都是通过 MBeans 来完成的。
所有的 MBeans 都需要注册到 MBeanServer 上,然后再通过一些外部工具如 JMX、Web 浏览器等等,就可以去获取或者修改 MBeans 的信息了。
这里的 MBean Server 是一个代理,它提供了一个注册、检索和操作 MBeans 的 API。它是 JMX 架构中的核心组件,负责管理所有 MBeans 的生命周期。
二 代码实践
接下来松哥通过一个简单的案例,来和大家演示一下如何通过 JMX + jconsole 工具实现动态修改线程池信息。
首先我们先来自定义一个动态线程池:
public class DynamicThreadPool { private ThreadPoolExecutor threadPoolExecutor; public DynamicThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } public ThreadPoolExecutor getThreadPoolExecutor() { return threadPoolExecutor; } public void setCorePoolSize(int corePoolSize) { threadPoolExecutor.setCorePoolSize(corePoolSize); } public void setMaximumPoolSize(int maximumPoolSize) { threadPoolExecutor.setMaximumPoolSize(maximumPoolSize); } }
这个动态线程池实际上就是把我们传统的线程池对象 ThreadPoolExecutor 封装了一下,并且提供了两个方法 setCorePoolSize 和 setMaximumPoolSize,通过这两个方法我们可以动态设置线程池的线程数。
接下来我们自定义一个 MBean 接口,这个接口中提供四个方法,分别用来获取或者设置线程数的信息。
public interface DynamicThreadPoolMXBean { int getCorePoolSize(); void setCorePoolSize(int corePoolSize); int getMaximumPoolSize(); void setMaximumPoolSize(int maximumPoolSize); }
最后,我们自定义类实现 DynamicThreadPoolMXBean 接口,并继承 StandardMBean 类,如下:
public class DynamicThreadPoolMBean extends StandardMBean implements DynamicThreadPoolMXBean { private DynamicThreadPool dynamicThreadPool; public DynamicThreadPoolMBean(DynamicThreadPool dynamicThreadPool) throws Exception { super(DynamicThreadPoolMXBean.class); this.dynamicThreadPool = dynamicThreadPool; registerMBean(); } private void registerMBean() { try { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName("org.javaboy:type=DynamicThreadPool"); mbs.registerMBean(this, name); } catch (Exception e) { e.printStackTrace(); } } @Override public int getCorePoolSize() { return dynamicThreadPool.getThreadPoolExecutor().getCorePoolSize(); } @Override public void setCorePoolSize(int corePoolSize) { dynamicThreadPool.setCorePoolSize(corePoolSize); } @Override public int getMaximumPoolSize() { return dynamicThreadPool.getThreadPoolExecutor().getMaximumPoolSize(); } @Override public void setMaximumPoolSize(int maximumPoolSize) { dynamicThreadPool.setMaximumPoolSize(maximumPoolSize); } }
这个类也没啥神奇的地方,唯一要注意的是,在构造器中,我们调用了 registerMBean 方法,这个方法用来将当前对象注册到 MBeanServer 上。
最后,我们就可以启动自己的这段代码了:
public class Main { public static void main(String[] args) throws Exception { DynamicThreadPool dynamicThreadPool = new DynamicThreadPool(2, 4, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10)); DynamicThreadPoolMBean mBean = new DynamicThreadPoolMBean(dynamicThreadPool); while (true) { System.out.println("CorePoolSize:" + dynamicThreadPool.getThreadPoolExecutor().getCorePoolSize()); System.out.println("MaximumPoolSize:" + dynamicThreadPool.getThreadPoolExecutor().getMaximumPoolSize()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
为了看到线程池的线程数量,我这里使用了一个死循环不停的打印线程数量信息,这样一会通过 jconsole 修改线程池信息的时候,我们就能看到修改的效果了。
程序启动之后,我们使用 jconsole 连接上当前应用程序,如下图:
在 MBeans 这个选项卡位置,我们可以看到刚刚配置的 MBean,右侧的 value 则可以直接修改,修改之后,回到应用程序控制台,我们会发现线程相关数据已经发生变化了。
可以看到,控制台信息已经发生变化。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java类是如何加载的?
@[toc] 有小伙伴最近在面试过程中遇到这样一个问题: Java 中的类是如何加载的? 这个问题还是很有意思,今天松哥来尝试和大伙梳理一下。 一 整体思路 整体上来说,类的加载主要是下面这几个步骤: 上面这张图就是一个类的完整生命周期了,一共要经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个不同的步骤。 这七个步骤中,验证、准备和解析一般又统一称之为 Linking。 这是整体的流程,接下来,松哥就和大家来分析每一个具体的步骤都干了啥。 二 Loading 首先第一步 Loading,也就是加载类。 这里如果被面试官细问,有两个方向: 什么时候加载? 怎么加载? 2.1 类的加载时机 先说类的加载时机。 如果需要一个权威的文档来说明问题,抱歉,官方没有任何文档来说明类在什么时候会被加载。但是,官方文档给出了六种类必须进行初始化的场景,毫无疑问,如果需要对类进行初始化,那么就必须先 Loading。 这六种场景分别是: n...
- 下一篇
前端性能调试实战:一次内存泄漏的排查与解决
"老王,我们的后台系统用着用着就变卡了,而且内存占用越来越大,是不是被攻击了?"上周四下午,运维小张一脸焦虑地找到我。作为项目的前端负责人,我立即打开了系统开始排查。 说实话,这个问题确实让我有点意外。我们的后台系统用 React 开发,平时运行都挺正常的,怎么突然就出现性能问题了?带着这个疑问,我开始了一场"破案"之旅。 问题的发现 首先,我让小张演示了一下具体的操作步骤。很快,我就发现了一些蛛丝马迹: 系统运行一段时间后,切换页面明显变慢 浏览器任务管理器显示内存占用持续上升 关闭标签页重新打开后,问题暂时消失 这些现象都指向一个可能:内存泄漏。但问题出在哪里呢? 调试工具的准备 我打开了 Chrome DevTools,开始系统性地排查: // 首先在代码中埋点,记录关键组件的生命周期 class SuspectComponent extends React.Component { componentDidMount() { console.time('ComponentLifetime') this._mountTime = performance.now() } compon...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS关闭SELinux安全模块
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6