Thread pool task monitoring for concurrent programming in Java
When we submit runnable or callable<?> to ThreadPoolExecutor, we cannot know when these tasks are actually executed. In order to achieve this requirement, we need to extend ThreadPoolExecutor, rewrite beforeExecute and afterExecute, in these two methods There are some related monitoring logic before and after task execution. There is also a terminated method, which is called back after the thread pool is closed. In addition, we can get the number of thread pools through getLargestPoolSize() and getCompletedTaskCount() respectively. Peak and thread pool completed tasks.
Here's a complete example to illustrate how:
Customize the MonitorHandler interface to abstract before and after:
package cc.lixiaohui.demo.concurrent; /** * Monitor processor, the purpose is to abstract before and after to form a monitor processor chain in {@link MonitorableThreadPoolExecutor} * * @author lixiaohui *@date October 11, 2016 at 7:18:38 PM * */ public interface MonitorHandler { /** * Change whether the monitoring task is available * * @return */ boolean usable(); /** * Callback before task execution * * @param thread the thread that will execute the task * @param runnable the task to be executed */ void before(Thread thread, Runnable runnable); /** * <pre> * Callback after task execution * Notice: * 1. When you submit a {@link Runnable} object to the thread pool, the parameter runnable is a {@link Runnable} object * 2. When you submit a {@link java.util.concurrent.Callable<?>} object to the thread pool, the parameter runnable is actually a {@link java.util.concurrent.FutureTask<?>} object * At this time, you can get the task execution result by setting the parameter runnable downcast to FutureTask<?> or Future * * @param runnable task after execution * @param throwable exception information */ void after(Runnable runnable, Throwable throwable); /** * Callback after the thread pool is closed * * @param largestPoolSize * @param completedTaskCount */ void terminated(int largestPoolSize, long completedTaskCount); }
Extend ThreadPoolExecutor and add monitoring logic. If monitoring is time-consuming, in order not to affect the execution efficiency of the business thread pool, we should encapsulate the calls of the before, after and terminated methods as a unified Runnable and hand it over to the Thread in the non-business thread pool. Run (create a new Thread or thread pool):
package cc.lixiaohui.demo.concurrent; import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * The monitorable thread pool can have multiple monitoring processors. If the monitoring logic is time-consuming, it is best to create a separate thread or thread pool to run the MonitorHandler method. * * @author lixiaohui *@date October 11, 2016 at 7:15:16 PM * */ public class MonitorableThreadPoolExecutor extends ThreadPoolExecutor { /** * Can have multiple monitoring processors */ private Map<String, MonitorHandler> handlerMap = new HashMap<String, MonitorHandler>(); private final Object lock = new Object(); public MonitorableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } public MonitorableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } public MonitorableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); } public MonitorableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); // call the handler in turn for (MonitorHandler handler : handlerMap.values()) { if (handler.usable()) { handler.before(t, r); } } } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); // call the handler in turn for (MonitorHandler handler : handlerMap.values()) { if (handler.usable()) { handler.after(r, t); } } } /* * @see java.util.concurrent.ThreadPoolExecutor#terminated() */ @Override protected void terminated() { super.terminated(); for (MonitorHandler handler : handlerMap.values()) { if (handler.usable()) { handler.terminated(getLargestPoolSize(), getCompletedTaskCount()); } } } public MonitorHandler addMonitorTask(String key, MonitorHandler task, boolean overrideIfExist) { if (overrideIfExist) { synchronized (lock) { return handlerMap.put(key, task); } } else { synchronized (lock) { return handlerMap.putIfAbsent(key, task); } } } public MonitorHandler addMonitorTask(String key, MonitorHandler task) { return addMonitorTask(key, task, true); } public MonitorHandler removeMonitorTask(String key) { synchronized (lock) { return handlerMap.remove(key); } } }
test program:
package cc.lixiaohui.demo.concurrent; import java.io.IOException; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import cc.lixiaohui.util.RandomUtils; /** * @author lixiaohui *@date October 11, 2016 at 8:11:39 PM * */ public class Tester { static volatile boolean stop = false; public static void main(String[] args) throws InterruptedException, IOException { // fixed size 5 final MonitorableThreadPoolExecutor pool = new MonitorableThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); pool.addMonitorTask("TimeMonitorTask", newTimeMonitorHandler()); // Start a thread and keep throwing tasks to the thread pool Thread t = new Thread(new Runnable() { public void run() { startAddTask (pool); } }); t.start(); // Lose the task and lose 20 ms Thread.sleep(50); stop = true; t.join(); pool.shutdown(); // Wait for the thread pool task to finish running pool.awaitTermination(100, TimeUnit.SECONDS); } private static MonitorHandler newTimeMonitorHandler() { return new MonitorHandler() { // The task start time record map, multi-threaded additions and deletions, need to use ConcurrentHashMap Map<Runnable, Long> timeRecords = new ConcurrentHashMap<Runnable, Long>(); public boolean usable() { return true; } public void terminated(int largestPoolSize, long completedTaskCount) { System.out.println(String.format("%s:largestPoolSize=%d, completedTaskCount=%s", time(), largestPoolSize, completedTaskCount)); } public void before(Thread thread, Runnable runnable) { System.out.println(String.format("%s: before[%s -> %s]", time(), thread, runnable)); timeRecords.put(runnable, System.currentTimeMillis()); } public void after(Runnable runnable, Throwable throwable) { long end = System.currentTimeMillis(); Long start = timeRecords.remove(runnable); Object result = null; if (throwable == null && runnable instanceof FutureTask<?>) { // An asynchronous task with a return value, not necessarily Callable<?>, but also Runnable try { result = ((Future<?>) runnable).get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // reset } catch (ExecutionException e) { throwable = e; } catch (CancellationException e) { throwable = e; } } if (throwable == null) { // task ends normally if (result != null) { // async task with return value System.out.println(String.format("%s: after[%s -> %s], costs %d millisecond, result: %s", time(), Thread.currentThread(), runnable, end - start, result)); } else { System.out.println(String.format("%s: after[%s -> %s], costs %d millisecond", time(), Thread.currentThread(), runnable, end - start)); } } else { System.err.println(String.format("%s: after[%s -> %s], costs %d millisecond, exception: %s", time(), Thread.currentThread(), runnable, end - start, throwable)); } } }; } // Random runnable or callable<?>, the task throws an exception randomly private static void startAddTask(MonitorableThreadPoolExecutor pool) { int count = 0; while (!stop) { if (RandomUtils.randomBoolean()) {// Lose the Callable<?> task pool.submit(new Callable<Boolean>() { public Boolean call() throws Exception { // throw exception randomly boolean bool = RandomUtils.randomBoolean(); // Random time 0~100 ms Thread.sleep(RandomUtils.randomInt(100)); if (bool) { throw new RuntimeException("thrown randomly"); } return bool; } }); } else { // 丢Runnable pool.submit(new Runnable() { public void run() { // Random time 0~100 ms try { Thread.sleep(RandomUtils.randomInt(100)); } catch (InterruptedException e) {} // throw exception randomly if (RandomUtils.randomBoolean()) { throw new RuntimeException("thrown randomly"); } }; }); } System.out.println(String.format("%s:submitted %d task", time(), ++count)); } } private static String time() { return String.valueOf(System.currentTimeMillis()); } }
A shorter result:
1476253228222: before[Thread[pool-1-thread-1,5,main] -> java.util.concurrent.FutureTask@548bb979] 1476253228222:Thread[Thread-0,5,main], submitted 1 task 1476253228253:Thread[Thread-0,5,main], submitted 2 task 1476253228264: before[Thread[pool-1-thread-2,5,main] -> java.util.concurrent.FutureTask@97e041d] 1476253228264:Thread[Thread-0,5,main], submitted 3 task 1476253228265: before[Thread[pool-1-thread-3,5,main] -> java.util.concurrent.FutureTask@7d6d5cc] 1476253228271: after[Thread[pool-1-thread-2,5,main] -> java.util.concurrent.FutureTask@97e041d], costs 7 millisecond, exception: java.util.concurrent.ExecutionException: java.lang.RuntimeException: thrown randomly 1476253228295: after[Thread[pool-1-thread-1,5,main] -> java.util.concurrent.FutureTask@548bb979], costs 42 millisecond 1476253228347: after[Thread[pool-1-thread-3,5,main] -> java.util.concurrent.FutureTask@7d6d5cc], costs 82 millisecond, exception: java.util.concurrent.ExecutionException: java.lang.RuntimeException: thrown randomly 1476253228347:Thread[pool-1-thread-3,5,main], largestPoolSize=3, completedTaskCount=3