1. 程式人生 > 實用技巧 >利用Java反射機制,獲取ThreadPoolExecutor執行緒池中的workers執行緒佇列

利用Java反射機制,獲取ThreadPoolExecutor執行緒池中的workers執行緒佇列

應用場景:
將若干有唯一任務Id的執行緒放到ThreadPoolExecutor中執行,在此之前儲存任務Id和執行緒的關係:Map<String, TestThread> taskIdCutThreadMap
在某些特殊情況下任務執行緒會異常崩潰而主程序無感知,此時taskIdCutThreadMap的size和ThreadPoolExecutor的poolsize數量就會對不上。

需求: 排查出哪些執行緒異常崩潰

解決方案:
1.執行緒設定定時心跳上報,當無心跳時則判斷執行緒死亡 2.為每個執行緒設定名為任務Id的執行緒名,然後定時獲取ThreadPoolExecutor執行緒池中執行緒集合,根據執行緒名排查出異常任務

方案一的實現比較簡單,本文探討方案二如何實現,即如何獲取ThreadPoolExecutor中執行的執行緒集合。

ThreadPoolExecutor中有一個私有集合物件workers,它是執行緒池中所有工作執行緒的集合。

集合存放的Worker類是定義在ThreadPoolExecutor中的內部類,原始碼就不貼了。

Worker類主要維護了4個引數:

1,final Thread thread;

thread屬性是Worker維護的執行緒,每個Worker物件一個,這個就是用來執行任務用的執行緒,也就是說,Worker物件的數量也就代表了執行緒池中活動執行緒的數量。

2,Runnable firstTask;

Worker物件的初始任務,多數情況下每個Worker物件建立時都伴隨一個初始任務,是這個Worker物件優先會執行的任務,當執行完firstTask後,Worker物件還會從執行緒池中繼續獲取任務並執行。

3,volatile long completedTasks;

該Worker物件執行完成的任務數。

4,private static final long serialVersionUID = 6138294804551838833L;

序列化UID,沒什麼用,註釋寫的也很坦誠,用不上,加這個引數就是為了不想看到Java的警告。

在本次需求中,我們要獲取的就是ThreadPoolExecutor中所有工作執行緒的集合workers以及集合裡每個Worker類的thread屬性物件。

但是由於workers是被private修飾的,ThreadPoolExecutor也沒有提供獲取workers的公共方法,所以我們需要通過java的反射機制去主動獲取這個屬性值並完成解析。

以下是程式碼:

/**
     * @throws Exception
     * @Description: 獲取ThreadPoolExecutor中私有HashSet集合物件workers的資料
     * @Param:
     * @return:
     * @author: hw
     * @date: 2020/12/15 16:54
     */
    public static void getThreadPoolWorkers(ThreadPoolExecutor cutExecutor) {
        //1.獲取私有物件workers
        Object resultValue = getPrivateClass(cutExecutor,"workers");
        HashSet workers = (HashSet) resultValue;
        //2.遍歷workers集合
        Iterator iterator = workers.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            //3.獲取workers的私有屬性thread物件
            Object workerValue = getPrivateClass(obj,"thread");
            Thread workerThread = (Thread) workerValue;
            System.out.println("執行緒名:" + workerThread.getName());
        }
    }

    /**
    * @Description: 獲取類的某個私有屬性
    * @Param:
    * @return:
    * @throws Exception
    * @author: hw
    * @date: 2020/12/15 17:11
    */
    public static <T> Object getPrivateClass(T t, String param) {
        Object workerValue = null;
        Class workerCla = t.getClass();
        Field[] workerFields = workerCla.getDeclaredFields();
        for (Field workerField : workerFields) {
            // 私有屬性必須設定訪問許可權
            workerField.setAccessible(true);
            // 獲取私有物件的屬性名稱
            String workerName = workerField.getName();
            if (param.equals(workerName)) {
                try {
                    // 獲取私有物件的屬性
                    workerValue = workerField.get(t);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return workerValue;
    }

至此,通過獲取到的執行緒名和taskIdCutThreadMap比較即可判斷出哪些執行緒異常崩潰了。

參考資料:

1.Java的反射機制 呼叫私有方法私有屬性

2.獲取object物件中的屬性值

資料拓展:

1 什麼是反射

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性。
這種動態獲取資訊以及動態呼叫物件方法的功能稱為java語言的反射機制。

2 Java反射機制的功能

1)在執行時判斷任意一個物件所屬的類;
2)在執行時構造任意一個類的物件;
3)在執行時判斷任意一個類所具有的成員變數和方法;
4)在執行時呼叫任意一個物件的方法;
5)生成動態代理。

反射機制原理圖

3 Class類

Java類均繼承了Object類,因此,可以利用在Object類中已經定義的getClass()方法,返回一個型別為Class的物件。

常用方法
getMethods()——獲取所有許可權為public的方法
getMethod(String methodName,Class<?> …paramTypes)——根據方法名、各入參的型別,返回許可權為public的指定方法
getDeclaredMethods()——獲取所有方法
getDeclaredMethod(String methodName,Class<?> …paramTypes) ——根據方法名、各入參的型別,返回指定方法

同樣,對於成員變數、內部類的訪問方法,也分為限定許可權public與否。

區別:
由此可見,在通過方法getMethods()依次獲取許可權為public的方法時,將包含從超類中繼承的方法;而通過方法getDeclaredMethods()只是獲得在本類中定義的方法。
此規則同樣適用於getFields()及getDeclaredFields()。

4 反射訪問成員變數

在通過方法訪問成員變數時,將返回Field型別的物件或陣列。每個Field物件代表一個成員變數,利用Field物件可以操縱相應的成員變數。
方法:
getFields()
getField(String name)
getDeclaredFields()
getDeclaredField(String name)

常用方法
getName()——獲得該成員變數的名稱
getType() ——獲得表示該成員變數型別的Class物件
get(Object obj) ——獲得指定物件obj中成員變數的值,返回值為Object型
set(Object obj,Object value) ——將指定物件obj中的成員變成的值設定為value
getInt(Object obj) ——獲得指定物件obj中型別為int的成員變數的值
setInt(Object obj,int i) ——將指定物件obj中型別為int的成員變數的值設定為i
getFloat(Object obj) ——獲得指定物件obj中型別為float的成員變數的值
setFloat(Object obj,float f) ——將指定物件obj中型別為float的成員變數的值設定為f
getBoolean(Object obj) ——獲得指定物件obj中型別為boolean的成員變數的值
setBoolean(Object obj,boolean z) ——將獲得指定物件obj中型別為boolean的成員變數的值設定為z
setAccessible(Boolean flag) ——可以設定忽略許可權限制直接訪問private等私有許可權的成員變數
getModifiers() ——獲得可以解析出該成員變數所採用的的修飾符的整數

set(Object obj,Object value)、setInt(Object obj,int i)、setFloat(Object obj,float f) 、setBoolean(Object obj,boolean z)僅能對許可權為public、protected的成員變數進行操作。
此時,需要利用setAccessible(Boolean flag)方法,設定為setAccessible(true),此時便可對private許可權的成員變數進行修改。

5 反射訪問方法

在通過反射機制訪問方法時,將返回Method型別的物件或陣列。每個Method物件代表一個成員變數,利用Method物件可以操縱相應的成員變數。
方法:
getMethods()
getMethod (String name,Class<?>…paramTypes)
getDeclaredMethods()
getDeclaredMethod (String name,Class<?>…paramTypes)

如果是訪問指定的方法,需要根據該方法的名稱和入參的型別來訪問。
例如,訪問一個名稱為test、入參型別依次為int、String、boolean的方法,可以通過以下兩種方式實現:
1)objClass.getDeclaredMethod(“test”,int.class,String.class,boolean.class);
2)objClass.getDeclaredMethod(“test”,new Class[]{int.class,String.class,boolean.class});

常用方法
getName()——獲得該方法的名稱
getParameterType() ——按照申明順序,以Class陣列的形式獲得各個引數型別
getReturnType() ——以Class物件的形式獲得該方法的返回值
getExceptionTypes()——以Class陣列的形式獲得該方法可能丟擲的異常型別(包括try…catch、throws)
invoke(Object obj,Object…args)——利用指定引數args,執行指定物件obj中的方法,返回值為Object型別
isVarArgs()——檢視該方法是否允許帶有可變數量的引數,允許返回true
getModifiers() ——獲得可以解析出該成員變數所採用的的修飾符的整數