java使用默认线程池踩过的坑(一)

本文探讨了一个在处理大量txt文件时遇到线程阻塞问题的解决方案。通过引入TaskManager,实现对任务线程的实时监控,并在特定条件下采取措施中止并重启线程,以避免任务堆积。重点解决了线程池调度器缺乏业务运行状态监控能力的问题,旨在提高任务执行的健壮性和效率。
部署运行你感兴趣的模型镜像

云智慧(北京)科技有限公司 陈鑫
场景
一个调度器,两个调度任务,分别处理两个目录下的txt文件,某个调度任务应对某些复杂问题的时候会持续特别长的时间,甚至有一直阻塞的可能。我们需要一个manager来管理这些task,当这个task的上一次执行时间距离现在超过5个调度周期的时候,就直接停掉这个线程,然后再重启它,保证两个目标目录下没有待处理的txt文件堆积。

无

问题
直接使用java默认的线程池调度task1和task2.由于外部txt的种种不可控原因,导致task2线程阻塞。现象就是task1和线程池调度器都正常运行着,但是task2迟迟没有动作。
当然,找到具体的阻塞原因并进行针对性解决是很重要的。但是,这种措施很可能并不能完全、彻底、全面的处理好所有未知情况。我们需要保证任务线程或者调度器的健壮性!
方案计划
线程池调度器并没有原生的针对被调度线程的业务运行状态进行监控处理的API。因为task2是阻塞在我们的业务逻辑里的,所以最好的方式是写一个TaskManager,所有的任务线程在执行任务前全部到这个TaskManager这里来注册自己。这个TaskManager就负责对于每个自己管辖范围内的task进行实时全程监控!

无
后面的重点就是如何处理超过5个执行周期的task了。
方案如下:
一旦发现这个task线程,立即中止它,然后再次重启;
一旦发现这个task线程,直接将整个pool清空并停止,重新放入这两个task ——【task明确的情况下】;
方案实施
中止后重启
Task实现类
classFileTask extends Thread {
private long lastExecTime = 0;
protected long interval = 10000;

public long getLastExecTime() {
    returnlastExecTime;
}

public void setLastExecTime(longlastExecTime) {
    this.lastExecTime =lastExecTime;
}

public long getInterval() {
    return interval;
}

public void setInterval(long interval) {
    this.interval = interval;
}

public File[] getFiles() {
    return null;
}

@Override
public void run() {
    while(!Thread.currentThread().isInterrupted()) {
        lastExecTime = System.currentTimeMillis();
       System.out.println(Thread.currentThread().getName() + " is running ->" + new Date());
        try {
           Thread.sleep(getInterval() * 6 * 1000);
        } catch(InterruptedException e) {
       Thread.currentThread().interrupt();
           e.printStackTrace();    // 当线程池shutdown之后,这里就会抛出exception了
        }
    }
}

}
TaskManager
public class TaskManager implements Runnable {
private final static Log logger = LogFactory.getLog(TaskManager.class);

public Set<FileTask> runners = newCopyOnWriteArraySet<FileTask>();

ExecutorService pool =Executors.newCachedThreadPool();

public voidregisterCodeRunnable(FileTask process) {
runners.add(process);
}

publicTaskManager (Set<FileTask>runners) {
    this.runners = runners;
}

@Override
public void run() {
    while(!Thread.currentThread().isInterrupted()) {
        try {
           long current = System.currentTimeMillis();
           for (FileTask wrapper : runners) {
               if (current - wrapper.getLastExecTime() >wrapper.getInterval()* 5) {
                   wrapper.interrupt();
                   for (File file : wrapper.getFiles()) {
                       file.delete();
                   }
                wrapper.start();  
               }
            }
        } catch(Exception e1) {
           logger.error("Error happens when we trying to interrupt and restart a task");
           ExceptionCollector.registerException(e1);
        }
        try {
           Thread.sleep(500);
        } catch(InterruptedException e) {
        }
    }
}

}
这段代码会报错java.lang.Thread IllegalThreadStateException。为什么呢?其实这是一个很基础的问题,您应该不会像我一样马虎。查看Thread.start()的注释, 有这样一段:
It is never legal to start a thread more thanonce. In particular, a thread may not be restarted once it has completedexecution.

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

### Java 默认线程池与自定义线程池 #### 默认线程池使用方式 Java 提供了 `Executors` 工厂类用于快速创建几种常见的线程池。这些预定义的线程池适用于特定类型的并发需求。 - **Fixed Thread Pool** 创建具有固定数量工作线程的线程池。当所有线程都在忙碌状态时,新任务将在队列中等待直到有空闲线程处理它们。 ```java ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); ``` - **Cached Thread Pool** 这种线程池会根据需要创建新的线程,但在先前构建的线程可用时将重用它们。对于执行大量短期异步任务的应用程序非常有用。 ```java ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ``` - **Scheduled Thread Pool** 支持延迟和定期执行任务的功能。可以用来安排命令在未来某个时间点运行次或重复多次。 ```java ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); ``` - **Single Thread Executor** 只有个工作者线程;它确保所有的任务都是按顺序完成,并且不会同时发生竞争条件。 ```java ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); ``` 以上提到的各种默认线程池都有其特点,在某些情况下可能已经足够满足应用的需求[^3]。 #### 自定义线程池使用方式 然而,默认线程池并不总是最佳选择,特别是在面对复杂业务逻辑的情况下。这时可以通过继承 `ThreadPoolExecutor` 类来自定义更灵活的线程池实现: ```java import java.util.concurrent.*; public class CustomThreadPool { private final ThreadPoolExecutor executor; public CustomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { this.executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<>(), new CustomThreadFactory(), // 定制化线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 配置拒绝策略 ); } public Future<?> submit(Runnable task){ return executor.submit(task); } } ``` 在这个例子中,除了设置基本参数外还可以指定阻塞队列类型、线程工厂以及饱和政策等高级选项来更好地适应具体应用场景的要求[^2]。 #### 主要区别 主要的区别在于灵活性上。默认线程池提供了简单易用的方式来进行多线程编程,但对于些特殊场景下的性能优化和支持不够充分。而通过自定义线程池,则可以根据实际项目的负载特性调整各个配置项,从而达到更好的资源利用率和服务质量保障效果[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值