任务调度线程池

8.任务调度线程池

任务调度有两种:

  • 一种是延时执行一个任务(这个任务执行完了,这个任务就结束了);

  • 一种是定时执行一个任务(这个任务按照指定的时间间隔重复运行)

Timer

在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务

Timer使用实例

@Slf4j
public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task2");
            }
        };
        log.debug("main开始使用timer调度");
        // 使用 timer 添加两个任务,希望它们都在 1s 后执行
		// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);
        log.debug("main结束");
    }
}

执行结果

可以看到,task1和task2本来都是设置的延迟1s运行,但是timer运行task1需要花费1s,等task1运行完了,才运行task2。特别是如果task1抛出异常,那么程序就直接结束了,task2根本就不会运行了。

18:52:54.985 [main] DEBUG com.zzhua.test25.TestTimer - main开始使用timer调度
18:52:54.987 [main] DEBUG com.zzhua.test25.TestTimer - main结束
18:52:55.987 [Timer-0] DEBUG com.zzhua.test25.TestTimer - task1
18:52:56.988 [Timer-0] DEBUG com.zzhua.test25.TestTimer - task2
ScheduledExecutorService

使用ScheduledExecutorService执行

鉴于Timer的不足,ScheduledExecutorService做了改进。

当任务调度线程池中有充足的线程时,ScheduledExecutorService不会像Timer一样等其中一个任务完成了,才运行其它任务。从执行结果来看,可以看到task1和task2都是在1s后执行的,此时线程数是够的。

@Slf4j
public class TestScheduledPool {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);

        pool.schedule(() -> {        // 1s之后,执行改任务
            log.debug("task1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, TimeUnit.SECONDS);

        pool.schedule(() -> {
            log.debug("task2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, TimeUnit.SECONDS);
    }
}
/*执行结果*/
21:04:06.721 [pool-1-thread-2] DEBUG com.zzhua.test25.TestScheduledPool - task2
21:04:06.720 [pool-1-thread-1] DEBUG com.zzhua.test25.TestScheduledPool - task1

当把上面的线程数改为1时,即任务调度线程池线程数不够时,的执行结果,可以看到出现任务串行执行的效果,即虽然两个任务都是1s后执行,但是变成了等待其中一个任务执行完了才会执行另外一个任务,因为线程数不够用嘛

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

/*执行结果*/
21:06:52.833 [pool-1-thread-1] DEBUG com.zzhua.test25.TestScheduledPool - task1
21:06:53.835 [pool-1-thread-1] DEBUG com.zzhua.test25.TestScheduledPool - task2

现在只有一个线程,我们再让其中一个任务抛出异常,发现根本就没任何反应,即抛出异常的任务直接就停了,然后这个任务调度线程池中的唯一一个线程又去调度另外的一个任务去了

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        pool.schedule(() -> {
            log.debug("task1");
            int i = 1 / 0;
            log.debug("task1...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, TimeUnit.SECONDS);

        pool.schedule(() -> {
            log.debug("task2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, TimeUnit.SECONDS);
/*执行结果*/
21:14:14.970 [pool-1-thread-1] DEBUG com.zzhua.test25.TestScheduledPool - task1
21:14:14.972 [pool-1-thread-1] DEBUG com.zzhua.test25.TestScheduledPool - task2

定时执行

如果定时任务要求,延后1s执行,之后每隔1s执行1次任务,并且线程池中线程数量充足。

  • 我们可以看到线程池中有2个线程,然后提交的任务是延迟1s开始这个任务,并且每隔1s执行一次这个任务,但是这个任务需要花费2s的时间。从结果上来看,任务的确是1s之后开始执行的,并且是每隔2s执行一次,并且线程池中是只有1个线程去处理这个任务,可是我们原先设定的是每隔1s执行一次。这样做,可以让任务不会重叠。
@Slf4j(topic = "c.ScheduledPool")
public class TestScheduledPool {
    public static void main(String[] args) {
        
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        
        log.debug("start...");
        
        pool.scheduleAtFixedRate(() -> {
            
            log.debug("任务执行开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
        }, 1000, 1000, TimeUnit.MILLISECONDS); 

        log.debug("main ends");
        
    }
}
/*输出结果*/ // 可以看到每隔2s做一次任务,并且是同一个线程去做的
/*
09:21:45.080 [main] DEBUG c.ScheduledPool - start...
09:21:45.151 [main] DEBUG c.ScheduledPool - main ends
09:21:46.152 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:21:48.152 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:21:50.152 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:21:52.153 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:21:54.153 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
*/

如果我们在定时任务中有2个任务,但是线程只有1个,即线程数量不够?

  • 本来2个任务,应该要让2个线程来做的,那就会出现每个线程分摊一个任务,然后每隔线程每隔2s各执行一次线程负责的任务;
  • 但问题是现在线程数量不够,2个任务出现了串行执行的效果,并且都是每隔2s执行一次
@Slf4j(topic = "c.ScheduledPool")
public class TestScheduledPool {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        log.debug("start...");
        pool.scheduleAtFixedRate(() -> {
            log.debug("任务执行开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);

        pool.scheduleAtFixedRate(() -> {
            log.debug("任务执行开始-2");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);
        log.debug("main end...");
    };
}
/*执行结果*/
/*
09:27:26.555 [main] DEBUG c.ScheduledPool - start...
09:27:26.619 [main] DEBUG c.ScheduledPool - main end...
09:27:27.618 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:27:29.619 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始-2
09:27:31.620 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:27:33.621 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始-2
09:27:35.622 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:27:37.622 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始-2
09:27:39.623 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
*/

如果定时任务中的一个任务抛出异常了呢?

  • 线程池中只有1个线程,有2个定时任务,其中一个任务抛出异常,然后观察执行结果,可以看到抛出异常的任务被放弃了
@Slf4j(topic = "c.ScheduledPool")
public class TestScheduledPool {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        log.debug("start...");
        pool.scheduleAtFixedRate(() -> {
            log.debug("任务执行开始");
            try {
                Thread.sleep(2000);
                int i = 1 / 0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);

        pool.scheduleAtFixedRate(() -> {
            log.debug("任务执行开始-2");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);
        log.debug("main end...");
    };
}

/*执行结果*/
09:39:05.983 [main] DEBUG c.ScheduledPool - start...
09:39:06.047 [main] DEBUG c.ScheduledPool - main end...
09:39:07.047 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始
09:39:09.047 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始-2
09:39:11.048 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始-2
09:39:13.049 [pool-1-thread-1] DEBUG c.ScheduledPool - 任务执行开始-2

SchedulerExecutorService还提供了在一个任务结束后,延迟指定的时间后执行任务的方法。感觉这样特别适合不知道任务需要花多久执行的情况。

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,  // 初始延迟
                                                 long delay,         // 任务结束后,延迟多久,再执行任务
                                                 TimeUnit unit);
总结
  • 任务调度线程池中的线程状态有2种,要么够,要么不够

    • 如果线程数量充足,那么每个任务都可以分配一个线程,按照给定的规则执行
    • 如果线程数量不够,那么线程要做2个任务,会在完成一个任务之后,再去做其它任务,出现2个任务串行执行的效果
  • 如果线程在执行某个任务时,任务种抛出了异常,那么线程就会抛弃这个任务,这个任务也不会被其它线程执行,并且抛弃这个任务的线程,又去调度别的任务去了

  • 定时任务设定的时间间隔内,如果还没有完成任务,那么会在任务完成时,执行任务,保证任务不会出现重叠。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值