add ThreadPoolExhaustedEvent and publish it in AbortPolicyWithReport.rejectedExecution method
user can listen ThreadPoolExhaustedEvent to do some alert
private static final String OS_WIN_PREFIX = "win";
    private static final String OS_NAME_KEY = "os.name";
    private static final String WIN_DATETIME_FORMAT = "yyyy-MM-dd_HH-mm-ss";
    private static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd_HH:mm:ss";
    private static Semaphore guard = new Semaphore(1);
    public AbortPolicyWithReport(String threadName, URL url) {
        this.threadName = threadName;
        this.url = url;
    }
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED!" +
                " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: "
                + "%d)," +
                " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
            threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),
            e.getLargestPoolSize(),
            e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
            url.getProtocol(), url.getIp(), url.getPort());
        logger.warn(msg);
        dumpJStack();
        throw new RejectedExecutionException(msg);
    }
    private void dumpJStack() {
        long now = System.currentTimeMillis();
        
        if (now - lastPrintTime < TEN_MINUTES_MILLS) {
            return;
        }
        if (!guard.tryAcquire()) {