Java中的线程池(4)----ScheduledThreadPool

本文对比分析了Java中的ScheduledThreadPool与Timer在处理定时任务时的不同表现,指出了Timer的局限性和ScheduledThreadPool的优点。

本文探讨Java线程池中的ScheduledThreadPool

ScheduledThreadPool可以用来处理延时任务或者定时任务。

过时的处理延时任务或者定时任务的类:Timer

我们关于定时/周期操作都是通过Timer来实现的。Timer简单易用,但是Timer存在一些危险:

1、Timer是基于绝对时间的,容易受到系统时钟的影响。

2、Timer只新建一个线程来执行所有的TimerTask,所以前面的任务会影响到后面的任务。

3、Timer不会捕获TimerTask的异常,只是简单地停止。因此会影响到其他TimeTask的执行。


对于Timer的一个对象timer,

timer.schedule(task, delay, period);可以延时delay后执行实现继承了TimerTask的任务,之后每过period再执行以此task;

timer.schedule(task, delay);可以延时delay后执行task。

我们来测试一下Timer,让我们看看它的缺点:

我们不测试它与系统时钟的关系了,先测试前面的任务会影响后面的任务的问题,

首先建立一个MyTimerTask类来定义Timer任务:

package newScheduledThreadPool;

import java.util.TimerTask;

public class MyTimerTask extends TimerTask {
    
    private int i ;
    private long time;//记录运行时间
    public MyTimerTask(int i,long time){
        this.i = i;
        this.time = time;
    }
    @Override
    public void run() {
        i++;
        System.out.println(Thread.currentThread().getName()+" "+i+"执行时间为"+(System.currentTimeMillis()-time));
        try {
            Thread.sleep(1000);//线程暂停一会儿
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
然后再Test类中做测试:

package newScheduledThreadPool;

import java.util.Timer;
import java.util.TimerTask;

public class Test {
    public static void main(String[] args) {
        Timer timer = new Timer();
        int i = 0;
        long time = System.currentTimeMillis();
        timer.schedule(new MyTimerTask(i,time){}, 1000);
        timer.schedule(new MyTimerTask(i,time){}, 1000);//观察前一个timer任务时间对这个任务时间的影响
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        timer.cancel();//关闭timer
    }
}
运行结果如下:


很明显后面的任务的执行时间受到了前面任务的影响,这是Timer的一个缺点。

另外来测试抛出异常Timer就简单停止的问题:

还是首先写一个MyTimeTask类定义Timer任务:

package newScheduledThreadPool;

import java.util.TimerTask;

public class MyTimerTask extends TimerTask {
    
    private int i ;
    public MyTimerTask(int i){
        this.i = i;
    }
    @Override
    public void run() {
        i++;
        if(i==2)
            throw new RuntimeException("i为2");
        System.out.println(Thread.currentThread().getName()+" "+i));
    }

}
然后使用下面的代码启动Timer进行测试(我就不写包名,类名了):

        Timer timer = new Timer();
        int i = 0;
        int j = 0;
//        long time = System.currentTimeMillis();

        timer.schedule(new MyTimerTask(i){}, 1000,1000);//i加到2会抛出异常
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        timer.schedule(new MyTimerTask(j){}, 1000);

运行结果如下:


很明显当任务抛出异常,Timer就cancel了。


下面介绍ScheduledThreadPool:

ScheduledThreadPool内部使用的阻塞队列是DelayQueue,这是一个无界、带延迟的阻塞队列,只有当延迟时间过了才能从这个阻塞队列中取出当中的元素。

对于newScheduledExecutorService()这个Executors的静态方法返回的SchedluedExecutorService类型对象 scheduledThreadPool,

scheduledThreadPool.schedule(command, delay, unit)表示过了delay的unit之后执行command

scheduledThreadPool.scheduleWithFixedDelay(command, initialDelay, delay, unit)表示首先过了initialDelay之后执行command,然后每过delay的unit之后再执行command。

下面用ScheduledThreadPool来解决Timer的缺点。

首先写实现Runnable接口的ThreadPoolTask类表示ScheduledThreadPool的执行任务:

package newScheduledThreadPool;

public class ThreadPoolTask implements Runnable {

    private long time;
    
    public ThreadPoolTask(long time) {
        this.time = time;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行"+" "+"用时"+(System.currentTimeMillis()-time));
        try {
            Thread.sleep(1000);//让线程休息一会儿
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}


然后用下面的代码进行测试(我就不写类名包名了):

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool();
long time = System.currentTimeMillis();
scheduledThreadPool.schedule(new ThreadPoolTask(time),1000,TimeUnit.MILLISECONDS);
scheduledThreadPool.schedule(new ThreadPoolTask(time),1000,TimeUnit.MILLISECONDS);//观察上个任务对这个任务的影响
try {
    TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
    scheduledThreadPool.shutdown();//关闭线程池
运行结果如下:


很明显前面的任务的执行时间没有影响到后面的任务。


另外测试scheduledThreadPool中线程在运行时抛出异常后面的任务还能不能进行:

首先改一下ThreadPoolTask类:

package newScheduledThreadPool;

public class ThreadPoolTask implements Runnable {

    private int i;
    
    public ThreadPoolTask(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        if(i==2)
            throw new IllegalStateException("i=2");//当i=2的时候抛出异常 观察对后面的线程池中线程的任务执行的影响
        System.out.println(Thread.currentThread().getName()+"执行"+" "+i++);
    }

}

然后在测试类中进行测试(不写包名类名了):

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);//相当于定时的FixedThreadPool
scheduledThreadPool.scheduleWithFixedDelay(new ThreadPoolTask(i), 1000,1000, TimeUnit.MILLISECONDS);
try {
    System.out.println("开始暂停");
    Thread.sleep(3000);//在主线程sleep期间 scheduledThreadPool中的线程会抛出异常 观察对下面线程池执行定时任务的影响
    System.out.println("暂停结束");
} catch (InterruptedException e) {
        e.printStackTrace();
}
scheduledThreadPool.scheduleWithFixedDelay(new ThreadPoolTask(j), 1000, 1000, TimeUnit.MILLISECONDS);
运行结果如下:

很明显,前面线程池中的线程在运行期间抛出异常并没有影响到后面的任务执行,这和Timer不一样。

以上就是ScheduledThreadPool基础以及ScheduledThreadPool和Timer的区别。

### Java线程池的使用方法及示例代码 Java 中的线程池主要用于管理和重用线程资源,从而提高应用程序的性能和响应速度。以下是关于线程池的具体实现方式及相关示例代码。 #### 一、线程池的核心概念 线程池的主要目的是减少每次创建新线程所带来的开销,并允许更高效地管理并发任务。核心组件包括 `Executor` 接口及其子接口 `ExecutorService`,以及具体的实现类如 `ThreadPoolExecutor` 和一些便捷工厂方法(如 `Executors.newFixedThreadPool()` 等)[^1]。 --- #### 二、常用的线程池类型及其实现 ##### 2.1 FixedThreadPool 此线程池具有固定的线程数量,超出的任务会被放入队列中等待执行。 ```java // 创建一个固定大小为3的线程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { final int taskNumber = i; fixedThreadPool.execute(() -> { System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName()); }); } fixedThreadPool.shutdown(); // 关闭线程池 ``` 这种方式适用于需要严格控制并发线程数的情况[^2]。 --- ##### 2.2 CachedThreadPool 此类线程池会根据需要动态创建新的线程,但在空闲时会回收未使用的线程。 ```java ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int taskIndex = i; cachedThreadPool.submit(() -> { try { Thread.sleep(100); // 模拟耗时操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Executing Task " + taskIndex); }); } cachedThreadPool.shutdown(); ``` 适合短生命周期的小型任务场景。 --- ##### 2.3 SingleThreadExecutor 仅有一个工作线程,按顺序依次执行提交的任务。 ```java ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); singleThreadExecutor.submit(() -> System.out.println("First Task")); singleThreadExecutor.submit(() -> System.out.println("Second Task")); singleThreadExecutor.shutdown(); ``` 常用于串行化任务处理。 --- ##### 2.4 ScheduledThreadPool 能够安排命令在未来某个时刻执行,也可以周期性地重复执行某项任务。 ```java ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2); scheduledThreadPool.schedule(() -> System.out.println("Delayed Task"), 2, TimeUnit.SECONDS); scheduledThreadPool.scheduleAtFixedRate(() -> { System.out.println("Periodic Task at " + LocalDateTime.now()); }, 0, 1, TimeUnit.SECONDS); scheduledThreadPool.shutdown(); ``` 对于定时任务非常有用。 --- ##### 2.5 WorkStealingPool (JDK 1.8 新增) 利用 ForkJoinPool 技术实现的工作窃取算法,旨在最大化 CPU 利用率。 ```java ExecutorService workStealingPool = Executors.newWorkStealingPool(); IntStream.rangeClosed(1, 5).forEach(i -> { workStealingPool.submit(() -> { System.out.println("Task-" + i + " executed by " + Thread.currentThread().getName()); }); }); workStealingPool.shutdown(); ``` 推荐在高吞吐量计算密集型环境中采用。 --- #### 三、手动创建线程池 (`ThreadPoolExecutor`) 如果默认提供的几种线程池无法完全满足业务需求,则可通过 `ThreadPoolExecutor` 自定义更多细节参数。 ```java ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor( 2, // 核心线程数 corePoolSize 4, // 最大线程数 maximumPoolSize 60L, // 空闲线程存活时间 keepAliveTime TimeUnit.SECONDS, // 时间单位 unit new LinkedBlockingQueue<>(100)); // 任务队列 queue customThreadPool.execute(() -> System.out.println("Custom Pool Example")); customThreadPool.shutdown(); ``` 通过调整这些参数可以灵活应对各种复杂情况。 --- ### 总结 以上分别介绍了五种典型的线程池类型及其适用范围,并提供了相应的代码片段帮助理解其基本用法。合理选用合适的线程池有助于优化系统的整体表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值