我們一起聊聊如何動態(tài)調(diào)試線程池?
這是有小伙伴最近在面深信服的時候遇到的一個問題,感覺比較有意思,松哥和大伙來聊一聊。
如何動態(tài)調(diào)試線程池?
面試官表示設(shè)置線程池核心線程數(shù)是一個非常具有挑戰(zhàn)性的事情,問有無辦法能夠動態(tài)的設(shè)置線程池核心數(shù),并觀察其執(zhí)行效果?
這個問題的難點在于它涉及到的技術(shù)點不是特別常用,該小伙伴面試的技術(shù)團(tuán)隊剛好是做運維工具的,做一些監(jiān)控軟件,所以剛好就問到這里。
那么松哥和大家簡單聊一聊這個話題。
其實這里主要是涉及到 Java 里邊一個比較古老的工具,JMX。
一、什么是 JMX
JMX(Java Management Extensions)是 Java 平臺的一部分,它提供了一種管理和監(jiān)控 Java 應(yīng)用程序的標(biāo)準(zhǔn)方法。JMX 允許你監(jiān)控和管理系統(tǒng)資源、應(yīng)用程序和服務(wù),以及獲取關(guān)于這些實體的運行時信息。
簡單來說,就是通過 JMX 可以動態(tài)查看對象的運行信息,并且可以動態(tài)修改對象屬性。
JMX 架構(gòu)如下圖:
圖片
分析這張圖我們可以發(fā)現(xiàn),JMX 底層是由很多不同的 MBeans 組成的,MBeans 是 JMX 的核心,它們是實現(xiàn)了特定接口的 Java 對象,用于表示可以被監(jiān)控和管理的資源。MBeans 可以分為四種不同的類型,分別是:
- Standard MBeans
- Dynamic MBeans
- Open MBeans
- Model MBeans
這些 MBeans 的作用就是獲取對象的信息,或者是修改對象信息,都是通過 MBeans 來完成的。
所有的 MBeans 都需要注冊到 MBeanServer 上,然后再通過一些外部工具如 JMX、Web 瀏覽器等等,就可以去獲取或者修改 MBeans 的信息了。
這里的 MBean Server 是一個代理,它提供了一個注冊、檢索和操作 MBeans 的 API。它是 JMX 架構(gòu)中的核心組件,負(fù)責(zé)管理所有 MBeans 的生命周期。
二、代碼實踐
接下來松哥通過一個簡單的案例,來和大家演示一下如何通過 JMX + jconsole 工具實現(xiàn)動態(tài)修改線程池信息。
首先我們先來自定義一個動態(tài)線程池:
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);
}
}
這個動態(tài)線程池實際上就是把我們傳統(tǒng)的線程池對象 ThreadPoolExecutor 封裝了一下,并且提供了兩個方法 setCorePoolSize 和 setMaximumPoolSize,通過這兩個方法我們可以動態(tài)設(shè)置線程池的線程數(shù)。
接下來我們自定義一個 MBean 接口,這個接口中提供四個方法,分別用來獲取或者設(shè)置線程數(shù)的信息。
public interface DynamicThreadPoolMXBean {
int getCorePoolSize();
void setCorePoolSize(int corePoolSize);
int getMaximumPoolSize();
void setMaximumPoolSize(int maximumPoolSize);
}
最后,我們自定義類實現(xiàn) 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);
}
}
這個類也沒啥神奇的地方,唯一要注意的是,在構(gòu)造器中,我們調(diào)用了 registerMBean 方法,這個方法用來將當(dāng)前對象注冊到 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();
}
}
}
}
為了看到線程池的線程數(shù)量,我這里使用了一個死循環(huán)不停的打印線程數(shù)量信息,這樣一會通過 jconsole 修改線程池信息的時候,我們就能看到修改的效果了。
程序啟動之后,我們使用 jconsole 連接上當(dāng)前應(yīng)用程序,如下圖:
圖片
在 MBeans 這個選項卡位置,我們可以看到剛剛配置的 MBean,右側(cè)的 value 則可以直接修改,修改之后,回到應(yīng)用程序控制臺,我們會發(fā)現(xiàn)線程相關(guān)數(shù)據(jù)已經(jīng)發(fā)生變化了。
圖片
可以看到,控制臺信息已經(jīng)發(fā)生變化。