Java线程池可靠实现(动态参数)

环境:

  • JDK8
  • SpringBoot 2.7.2
  • Lombok 1.18.20
  • HuTool 5.8.10
  • guava 27.1-jre

本文章是对Java线程池实现的一些基本方案,主要实现了以下几个功能:

  1. 自定义拒绝策略
  2. 任务重试机制(可配置具体细节)
  3. 快慢线程池(分别处理快慢任务)
  4. 动态调整线程池参数(只包含核心属性)
  5. 手动执行任务(针对单节点服务中定时任务)

线程池动态实现参考美团文章:Java线程池实践

本文章全部源码(觉得有用的话点个star吧!):https://github.com/demsiadh/ScheduleUtil

本文章不含测试内容,所有内容均已测试通过,感兴趣可以拉下来测一把

一、原生线程池的局限性

在程序中使用线程池,主要是为了多线程执行任务下对线程进行管理,同时减少了上下文切换提高了处理效率,但是目前原生线程池的实现方案并不可靠,包括拒绝策略,在项目中都是无法直接使用的

无法定义的线程池参数

原生线程池,在定义的时候就已经写死了核心线程数和最大线程数等属性,但是我们并没有一个公式或者可靠的方案来定义,虽然我们都知道IO密集型(2n)、计算密集型(n+1),但是在实际项目中错综复杂,无法定义,参数设置不当不仅享受不到线程池的优势,甚至还有可能导致故障。

不可靠的拒绝策略

在Java线程池中,拒绝策略在线程池无法处理新任务的时候触发(最大线程数和阻塞队列都已经满了),默认的拒绝策略有四个,他们的作用以及局限性如下:

  • AbortPolicy(默认策略)

​ - 作用:直接抛出异常

​ - 缺点:在项目中我们不希望任务被抛弃或者抛出异常

  • CallerRunsPolicy

​ - 作用:让调用者线程来执行任务

​ - 缺点:如果有大量任务都触发这个拒绝策略,对服务器的压力还是挺大的,甚至可能导致无法处理其他任务

  • DiscardOldestPolicy

​ - 作用:丢弃最早未处理的任务,让当前任务入队

​ - 缺点:在一个可靠的项目中,我们不希望任何一个任务被抛弃

  • DiscardPolicy

​ - 作用:直接丢弃当前任务

​ - 缺点:在一个可靠的项目中,我们不希望任何一个任务被抛弃

二、可靠线程池的实现

我们需要实现一个可靠线程池,就必须保证没有任务会被抛弃,并且能够处理各种任务

1.自定义实现

自定义拒绝策略

线程池的拒绝策略都实现了一个接口RejectedExecutionHandler,我们定义一个ScheduleRejected来实现这个接口,具体策略我们采用简单的队列的put方法(会使当前线程进入阻塞);

@Component
@Slf4j
public class ScheduleRejected implements RejectedExecutionHandler {

    /**
     * 当线程池满的时候,直接调用队列的put方法来进行阻塞
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     */
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            // 不会被打断
        }
    }
}

自定义阻塞队列

原生的阻塞队列由于其容量(capacity)都被final修饰,不可被修改,所以我们需要自行实现一个阻塞队列,为其添加get,set方法,为了保证线程安全需要添加volatile,实现方式也很简单,直接赋值jdk源码中的LinkedBlockingQueue的全部代码并修改名称ResizeLinkedBlockingQueue,并修改部分方法中的判断即可;

  1. capacity属性
    @Getter
    private volatile int capacity;

    public void setCapacity(int capacity) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.capacity = capacity;
    }
  1. 构造函数
    public ResizeLinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                // 在这里
                if (n >= capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }
  1. put方法
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            // 这里
            while (count.get() >= capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

4.offer方法

    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
                // 在这里
            while (count.get() >= capacity) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;
    }
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        // 在这里
        if (count.get() >= capacity)
            return false;
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }
  1. clear方法
    public void clear() {
        fullyLock();
        try {
            for (Node<E> p, h = head; (p = h.next) != null; h = p) {
                h.next = h;
                p.item = null;
            }
            head = last;
            // assert head.item == null && head.next == null;
            // 在这里
            if (count.getAndSet(0) >= capacity)
                notFull.signal();
        } finally {
            fullyUnlock();
        }
    }

2.线程池工具

可重试任务

  1. 判定是否成功:既然需要可以重试,所以我们需要传入的任务可以获取到运行结果,所以就采用Java的函数式编程来实现,这里我们定义一个消费者来消费传入的任务(taskId,是用于标识任务的唯一标志,使用时建议采用类名.方法名)
    // 消费者接口
    private interface DConsumer<T, R> {
        void accept(T t, R r);
    }
  
    // 消费者
    private final DConsumer<String, ScheduledRunnable> cff = (taskId, scheduledRunnable) -> {
        LocalDateTime startTime = LocalDateTime.now();
        log.info("{} task start", taskId);
        try {
            // 接受任务执行的返回值
            Boolean apply = scheduledRunnable.supplier.get();
            // 如果执行失败就重试
            if (!apply) {
                addTask(scheduledRunnable);
            }
        } catch (Exception e) {
            log.error("{} task fail Exception: {}", taskId, e.getMessage(), e);
        }finally {
            log.info("{} task finish!", taskId);
            // 记录当前任务执行时间
            TASK_TIME_MAP.put(taskId, LocalDateTimeUtil.between(startTime, LocalDateTime.now()).getSeconds());
        }
    };
    
    
  1. 内部记录次数和时间:既然是重试,肯定要有个重试次数和重试时间,由于定时调度采用的是ScheduledExecutorService,所以我们创建一个内部类实现Runnable,同时内部记录相关信息
public class ScheduledRunnable implements Runnable{
    private int retryTimes;
    private final String taskId;
    private final Supplier<Boolean> supplier;
    private final LocalDateTime deadlineTime;

    public ScheduledRunnable(String taskId, Supplier<Boolean> supplier, LocalDateTime deadlineTime) {
        this.taskId = taskId;
        this.supplier = supplier;
        this.deadlineTime = deadlineTime;
        retryTimes = 0;
    }
    @Override
    public void run() {
        // 如果重试次数超过最大次数或者超过最大重试时间则移除任务
        if (retryTimes++ >= scheduleConfig.getMaxRetryTimes()) {
            remove(taskId);
            log.error("{} task retry times is over max retry times!", taskId);
            return;
        } else if (LocalDateTime.now().isAfter(deadlineTime)) {
            remove(taskId);
            log.error("{} task retry times is over deadline time!", taskId);
            return;
        }
        log.info("{} task retry", taskId);
        // 执行任务 如果执行成功则移除任务
        boolean retryResult = supplier.get();
        if (retryResult) {
            remove(taskId);
        }
    }
}
  1. 添加任务到定时调度线程池中(FUTURE_MAP记录了所有在定时任务调度线程池中的任务)
private static ScheduledExecutorService service;
// 任务集合
private static final Map<String, ScheduledFuture<?>> FUTURE_MAP = new ConcurrentHashMap<>();
@Resource
private ScheduleConfig scheduleConfig;

@PostConstruct
public void init() {
    // 初始化线程工厂 设置为守护线程
    ThreadFactory threadFactory = new ThreadFactoryBuilder()
            .setNameFormat("schedule-pool-%d").setDaemon(true).build();
    // 如果定时线程池已经存在则先关闭
    if (service != null) {
        service.shutdown();
    }
    // 创建定时任务线程池 异常处理直接抛出异常
    service = new ScheduledThreadPoolExecutor(2, threadFactory, new ThreadPoolExecutor.AbortPolicy());
}

/**
 * 添加定时任务(一定时间后重新执行)
 *
 * @param retryTask 需要重试的任务对象
 */
private void addTask(ScheduledRunnable retryTask) {
    // 如果重试任务已经在map中则直接返回
    if (FUTURE_MAP.get(retryTask.taskId) != null) {
        log.error("ScheduledService add task Failed! {} task already exist", retryTask.taskId);
        return;
    }
    // 将任务添加到定时服务中
    ScheduledFuture<?> scheduledFuture = service.scheduleWithFixedDelay(retryTask, scheduleConfig.getRetryInterval(),
            scheduleConfig.getRetryInterval(), scheduleConfig.getRetryIntervalUnit());

    // 将任务添加到重试任务中
    FUTURE_MAP.put(retryTask.taskId, scheduledFuture);
}
  1. 移除任务
/**
 * 根据taskId 移除定时任务
 *
 * @param taskId 任务id
 */
private void remove(String taskId) {
    if (FUTURE_MAP.get(taskId) == null) {
        log.error("ScheduledService remove task Failed! {} task not exist", taskId);
        return;
    }
    ScheduledFuture<?> scheduledFuture = FUTURE_MAP.get(taskId);
    // 取消当前任务,但并不强制取消正在执行的任务
    boolean cancel = scheduledFuture.cancel(false);
    if (!cancel) {
        log.error("{} task cancel fail", taskId);
    }
    // 将当前任务从任务队列中删除
    FUTURE_MAP.remove(taskId);
}

配置重试参数

通过上面可以知道,重试次数和重试时间间隔,以及最大重试次数和最大重试时间都是配置而来的,这个类定义如下

/**
 * <big>定时任务配置类</big>
 *
 * @author 13684
 * @data 2024/5/27 下午5:02
 */
@ConfigurationProperties(prefix = "schedule")
@Component
@Data
public class ScheduleConfig {
    // 定时任务最大重试次数
    private int maxRetryTimes = 3;

    // 定时任务重试时间间隔
    private String retryIntervalStr = "MINUTES";
    // 定时任务重试时间
    private int retryInterval = 1;
    // 定时任务重试时间单位
    private TimeUnit retryIntervalUnit;

    // 定时任务重试最大时间
    private String maxRetryIntervalStr = "MINUTES";
    // 定时任务重试最大时间
    private int maxRetryInterval = 10;
    // 定时任务重试最大时间单位
    private TimeUnit maxRetryIntervalUnit;

    @PostConstruct
    public void init() {
        for (TimeUnit unit : TimeUnit.values()) {
            if (unit.name().equals(retryIntervalStr)) {
                retryIntervalUnit = unit;
            }
            if (unit.name().equals(maxRetryIntervalStr)) {
                maxRetryIntervalUnit = unit;
            }
        }
    }
}

快慢线程池

采用双线程池对任务进行处理,执行时间长的和执行时间短的分别处理(可以避免由于长任务占用线程导致频率较高的短任务压爆队列),不过任务第一次运行的时候我们还是用一个执行(因为我们需要记录时间,而不是依靠经验判断),当第二次时进行分流,阈值需要根据项目来估计

快慢线程池(采用了自定义队列和拒绝策略)定义如下:

// 长短任务的区分时间 10s
private static final Long MAX_TIME = 10L;
// 任务执行的默认耗时(用来处理任务第一次运行)
private static final Long DEFAULT_TIME = 100L;
// 记录执行时间,每次记录任务执行时间,用来分类
private static final Map<String, Long> TASK_TIME_MAP = new ConcurrentHashMap<>();
// 快速线程池,用来处理执行时间小于10s的任务(为了区分长任务和短任务)
private static final ThreadPoolExecutor QUICK_HANDLER_EXECUTOR = new ThreadPoolExecutor(2, 4, 30,
        TimeUnit.SECONDS, new ResizeLinkedBlockingQueue<>(500),
        new ThreadFactoryBuilder().setNameFormat("quickHandler-pool-%d").setDaemon(true).build(),
        new ScheduleRejected());
// 标准线程池,执行长任务,并且在开机时执行所有任务并统计时间
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(2, 5, 30,
        TimeUnit.SECONDS, new ResizeLinkedBlockingQueue<>(500),
        new ThreadFactoryBuilder().setNameFormat("standardHandler-pool-%d").setDaemon(true).build(),
        new ScheduleRejected());

在每次任务执行的时候计算时间并且put到map中

// 消费者
private final DConsumer<String, ScheduledRunnable> cff = (taskId, scheduledRunnable) -> {
    LocalDateTime startTime = LocalDateTime.now();
    log.info("{} task start", taskId);
    try {
        // 接受任务执行的返回值
        Boolean apply = scheduledRunnable.supplier.get();
        // 如果执行失败就重试
        if (!apply) {
            addTask(scheduledRunnable);
        }
    } catch (Exception e) {
        log.error("{} task fail Exception: {}", taskId, e.getMessage(), e);
    }finally {
        log.info("{} task finish!", taskId);
        // 记录当前任务执行时间
        TASK_TIME_MAP.put(taskId, LocalDateTimeUtil.between(startTime, LocalDateTime.now()).getSeconds());
    }
};

在调用任务执行(这也是整个线程池工具类的入口方法)时进行分流

/**
 * 执行重试的定时任务
 *
 * @param taskId   任务id
 * @param supplier 执行任务的方法
 */
public void getSupplier(String taskId, Supplier<Boolean> supplier) {
    // 创建重试任务
    ScheduledRunnable scheduledRunnable = new ScheduledRunnable(taskId, supplier, LocalDateTime.now().plusSeconds(scheduleConfig.getMaxRetryIntervalUnit().toSeconds(scheduleConfig.getMaxRetryInterval())));
    // 如果执行时间小于10s,放到快速线程池中
    if (TASK_TIME_MAP.getOrDefault(taskId, DEFAULT_TIME) <= MAX_TIME) {
        QUICK_HANDLER_EXECUTOR.execute(() -> cff.accept(taskId, scheduledRunnable));
    }else {
        THREAD_POOL_EXECUTOR.execute(() -> cff.accept(taskId, scheduledRunnable));
    }
}

动态调整线程池参数

在前面采用了我们自定义线程池队列后,加上线程池自带的修改参数的方法,我们现在已经可以修改线程池比较核心的属性,对于线程池参数修改采用web请求的方法进行修改

首先创建一个枚举类,获取项目中所有需要动态调整参数的线程池

public interface BaseEnum {
    /**
     * 获取一个值。
     *
     * @return 返回方法的数值。
     */
    int getValue();

    /**
     * 获取描述信息。
     *
     * @return 返回方法的描述字符串。
     */
    String getDesc();

    /**
     * 检查传入的整数值是否与当前对象的值相等。
     *
     * @param value 要比较的整数值。
     * @return 如果相等返回true,否则返回false。当传入的值为null时,总是返回false。
     */
    default boolean equalsValue(Integer value) {
        // 检查传入的value是否为null
        if (value == null) {
            return false;
        }
        // 比较当前对象的值和传入的值是否相等
        return getValue() == value;
    }

}

/**
 * <big>线程池枚举类</big>
 * <p>用来存储项目中所有的线程池</p>
 *
 * @author 13684
 * @data 2024/7/15 上午10:30
 */
public enum SchedulePoolEnum implements BaseEnum {
    QuickHandler(0, "快速线程池", ScheduledUtil.getQuickHandlerExecutor()),
    ThreadPool(1, "标准线程池", ScheduledUtil.getThreadPoolExecutor());
    ;
    final Integer value;
    final String desc;
    @Getter
    final ThreadPoolExecutor threadPoolExecutor;

    SchedulePoolEnum(Integer value, String desc, ThreadPoolExecutor threadPoolExecutor) {
        this.value = value;
        this.desc = desc;
        this.threadPoolExecutor = threadPoolExecutor;
    }

    @Override
    public int getValue() {
        return value;
    }

    @Override
    public String getDesc() {
        return desc;
    }

}

创建web请求实体(ak这里并没有实现,但是项目中一定要鉴权)

/**
 * <big>线程池DTO对象</big>
 * <p>用来动态修改线程池参数</p>
 *
 * @author 13684
 * @data 2024/7/15 上午10:23
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SchedulePoolDTO {
    // 核心线程数
    private int corePoolSize;
    // 最大线程数
    private int maximumPoolSize;
    // 队列容量大小
    private int queueCapacity;
    // 线程空闲时间
    private int keepAliveTime;
    // 线程空闲时间单位
    private TimeUnit keepAliveTimeUnit;
    // 访问id
    private String accessId;
    // 访问key(鉴权使用)
    private String accessKey;
}

最后定义web接口,这里实现了修改线程池参数以及获取线程池信息(可以开发到前端页面上,更加直观)

@Slf4j
@RestController
@RequestMapping("/schedule")
public class ScheduleController implements ApplicationContextAware {
    @PutMapping("/updatePool")
    public String updatePool(@RequestParam SchedulePoolEnum type, @RequestBody SchedulePoolDTO schedulePoolDTO) {
        try {
            // 获取线程池
            ThreadPoolExecutor threadPoolExecutor = type.getThreadPoolExecutor();
    
            // 设置核心线程数 (这里就直接if判断了,真实项目可以开发前端页面回显)
            if (ObjectUtil.isNotEmpty(schedulePoolDTO.getCorePoolSize())) {
                threadPoolExecutor.setCorePoolSize(schedulePoolDTO.getCorePoolSize());
            }
    
            // 设置最大线程数
            if (ObjectUtil.isNotEmpty(schedulePoolDTO.getMaximumPoolSize())) {
                threadPoolExecutor.setMaximumPoolSize(schedulePoolDTO.getMaximumPoolSize());
            }
    
            // 设置线程空闲时间
            if (!ObjectUtil.hasNull(schedulePoolDTO.getKeepAliveTime(), schedulePoolDTO.getKeepAliveTimeUnit())) {
                threadPoolExecutor.setKeepAliveTime(schedulePoolDTO.getKeepAliveTime(), schedulePoolDTO.getKeepAliveTimeUnit());
            }
    
            // 设置队列容量大小
            ResizeLinkedBlockingQueue<Runnable> queue = (ResizeLinkedBlockingQueue<Runnable>) threadPoolExecutor.getQueue();
            if (ObjectUtil.isNotEmpty(schedulePoolDTO.getQueueCapacity())) {
                queue.setCapacity(schedulePoolDTO.getQueueCapacity());
            }
    
            log.info("Update pool success! type={}, schedulePoolDTO={}", type, schedulePoolDTO);
            StringBuilder sb = new StringBuilder();
            for (SchedulePoolEnum value : SchedulePoolEnum.values()) {
                ResizeLinkedBlockingQueue<Runnable> tempQueue = (ResizeLinkedBlockingQueue<Runnable>) value.getThreadPoolExecutor().getQueue();
                sb.append(value.getDesc()).append(": corePoolSize=").append(value.getThreadPoolExecutor().getCorePoolSize())
                        .append(", maximumPoolSize=").append(value.getThreadPoolExecutor().getMaximumPoolSize())
                        .append(", keepAliveTime=").append(value.getThreadPoolExecutor().getKeepAliveTime(TimeUnit.SECONDS)).append("s")
                        .append(", queueCapacity=").append(tempQueue.getCapacity())
                        .append("\n");
            }
            log.info("All pool info: \n{}", sb);
        }catch (Exception e) {
            log.error("Update pool failed! type={}, schedulePoolDTO={}", type, schedulePoolDTO);
            log.error("Exception ", e);
            return "exception";
        }
        return "success";
    }
    
    @GetMapping("/getPool")
    public String getPool(@RequestParam SchedulePoolEnum type) {
        try {
            ThreadPoolExecutor threadPoolExecutor = type.getThreadPoolExecutor();
            ResizeLinkedBlockingQueue<Runnable> queue = (ResizeLinkedBlockingQueue<Runnable>) threadPoolExecutor.getQueue();
            return type.getDesc() + ": corePoolSize=" + threadPoolExecutor.getCorePoolSize() +
                    ", maximumPoolSize=" + threadPoolExecutor.getMaximumPoolSize() +
                    ", keepAliveTime=" + threadPoolExecutor.getKeepAliveTime(TimeUnit.SECONDS) +
                    ", queueCapacity=" + queue.getCapacity();
    
        }catch (Exception e) {
            log.error("Get pool failed! type={}", type);
            log.error("Exception ", e);
            return "exception";
        }
    }
}

题外话:主动调用任务

由于此文章的线程池问题出于一个单体服务(定时任务很多),出于业务要求,需要可以同步数据,所以提供一个可以手动调用任务的方法来达到此要求

如果不是单体服务,还是建议引入分布式任务调度框架

这个方法也作为一个web请求来实现,方法传入实体定义如下:

/**
 * <big>任务执行的dto对象</big>
 * <p>包含主动执行一个任务所需要的属性</p>
 *
 * @author 13684
 * @data 2024/7/15 上午10:18
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ScheduleTaskDTO {
    // 任务全类名
    private String className;
    // 任务方法名
    private String methodName;
    // 访问id
    private String accessId;
    // 访问key(鉴权使用)
    private String accessKey;
}

定义一个注解,无作用,专门用来标记当前类的方法可以通过主动调用的方式来执行

/**
 * <big>允许主动调用的标识</big>
 * <p>不做任何处理,仅仅用来标志,无此标志则不可主动调用</p>
 *
 * @author 13684
 * @data 2024/7/15 上午9:53
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface InitiativeExecute {
}

请求方法定义如下:

/**
 * 通过反射,主动调用定时任务(适用于单体架构,无分布式调度框架)
 * 需要进行鉴权,根据ak,或者其他方法进行鉴权
 *
 * @param scheduleTaskDTO   调度任务
 * @return  调用结果
 */
@PostMapping("/execute")
public String executeSchedule(@RequestBody ScheduleTaskDTO scheduleTaskDTO) {
    try {
        // 获取类
        Class<?> aClass = Class.forName(scheduleTaskDTO.getClassName());
        if (!aClass.isAnnotationPresent(InitiativeExecute.class)) {
            return "Class not annotated with @InitiativeExecute";
        }

        // 获取方法
        Method method = aClass.getDeclaredMethod(scheduleTaskDTO.getMethodName());

        // 获取访问权限
        method.setAccessible(true);

        // 获取实例
        Object bean = applicationContext.getBean(aClass);
        // 执行实例方法
        method.invoke(bean);
        log.info("Schedule execute success! ScheduleTaskDto={}", scheduleTaskDTO);
    } catch (Exception e) {
        log.error("Schedule execute failed! ScheduleTaskDto={}", scheduleTaskDTO);
        log.error("Exception ", e);
        return "exception";
    }
    return "success";
}

三、写在最后

当前实现的线程池依旧存在不足,如果有更好的实现方法欢迎私信我一起讨论交流。

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Java 中,我们可以通过使用线程池实现 socket 编程。下面是一个基本的示例代码: ```java import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SocketServer { private static final int PORT = 9999; private static final int THREAD_POOL_SIZE = 10; public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(PORT); ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); while (true) { Socket socket = serverSocket.accept(); // 等待客户端连接 SocketThread socketThread = new SocketThread(socket); executorService.execute(socketThread); // 将 SocketThread 放入线程池中执行 } } } class SocketThread implements Runnable { private Socket socket; public SocketThread(Socket socket) { this.socket = socket; } @Override public void run() { // 处理客户端请求 } } ``` 在上面的代码中,我们使用了线程池来处理客户端的连接请求。当有客户端连接时,我们将其放入一个自定义的 SocketThread 中,并将该线程放入线程池中执行。这样可以有效地避免了每个客户端连接都创建一个新的线程,从而提高了程序的性能和可靠性。 ### 回答2: Java提供了一个线程池(ThreadPoolExecutor)来管理线程,可以用来实现Socket通信。线程池中的线程可以复用,并且可以控制线程的数量,从而提高运行效率。 首先,我们需要创建一个ServerSocket,用于监听客户端的连接请求。然后,通过线程池创建一定数量的线程,等待客户端的连接。当有客户端连接到服务器时,线程池会从池中选择一个空闲的线程来处理这个连接。 在每个线程中,我们可以进行Socket通信,包括接收和发送数据。通过使用线程池,我们可以同时处理多个客户端的请求,而不是每个客户端都创建一个独立的线程。 当客户端的请求被处理完毕后,该线程会重新放回线程池中,等待下一个连接。通过使用线程池,我们可以减少线程的创建和销毁的开销,提高服务器的性能和资源利用率。 需要注意的是,使用线程池时,我们需要合理设置线程池的大小。如果线程池线程数量过小,可能会导致连接请求被拒绝或者请求处理速度较慢。而线程池线程数量过大,则可能会导致系统资源被过度占用。 总之,通过使用线程池实现Socket通信,可以提高服务器的并发处理能力和性能,同时也能够有效管理和控制线程的数量。 ### 回答3: Java中的Socket线程池可以通过使用java.util.concurrent包中的线程池实现线程池可以用来管理和调度多个线程,以便更有效地处理来自客户端的请求。 实现Socket线程池的步骤如下: 1. 创建一个线程池对象,可以使用ThreadPoolExecutor类来实现。该类可以接受一些参数,如核心线程数、最大线程数和线程空闲时间等。 2. 初始化线程池,可以通过调用ThreadPoolExecutor类的构造函数来完成。在初始化时,可以指定核心线程数、最大线程数、线程空闲时间和任务队列等参数。 3. 使用线程池执行任务,可以通过将任务封装为Runnable对象或Callable对象,并调用execute()或submit()方法来提交任务。线程池会自动从线程池中选择一个空闲的线程来执行任务。 4. 处理任务的执行结果,可以通过Future对象来获取任务的执行结果。可以使用Future.get()方法来获取任务的执行结果,该方法会阻塞当前线程,直到结果可用。 5. 关闭线程池,使用线程池之后,需要及时关闭。可以调用线程池的shutdown()方法来关闭线程池,该方法会等待所有任务执行完毕后再关闭线程池。 使用线程池可以实现线程处理Socket请求,提高系统的处理能力和响应速度。线程池会根据系统的负载情况自动调配线程,适当增加或减少线程数量,以提供最佳的性能和资源利用率。 总之,Java中的Socket线程池可以通过使用java.util.concurrent包中的线程池实现,通过合理配置线程池参数和处理任务的方式,可以实现更高效的多线程处理Socket请求的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值