首页
学习
活动
专区
圈层
工具
发布

单线程池,采用任务调度执行,居然执行了多个线程,踩了个大坑

开发中有个需求,在A页面每十秒调用一次A1任务,在B页面每十秒调用一次B1任务,A页面和B页面是互斥的,一次只能展示一个页面。因此,停留在哪个页面就调用哪个页面的任务。

做这个需求,我想到了单线程池+任务调度。

代码很快写好了,执行结果出乎意料。

当我点击A页面时,单线程池加入了A1任务,并开启了定时执行。当我点击B页面时,单线程池又加入了B1任务,并开启了定时执行。

也就是说,虽然我看得当前页面是B页面,但是后台却有两个任务在执行,无形中偷走了电脑的最宝贵资源---性能。

为什么会这样呢?说好的单线程呢?

1. 任务间的调度间隔

如果我们使用的是ScheduledExecutorService,并且提交了多个定时任务,它们的调度可能是在不同的时间点。

即使我们使用的是单线程池,每个任务会依次排队执行,前一个任务执行完毕之后,才会执行下一个任务。

所以,不同的任务可能在不同的时刻被触发执行,这也是我们看到多个任务在池中被执行的原因。

例如:

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

Runnable task1 = () -> {

  try {

      System.out.println("Task 1 executed");

      Thread.sleep(3000); // 模拟长时间任务

  } catch (InterruptedException e) {

      System.out.println("Task 1 interrupted");

  }

};

Runnable task2 = () -> {

  try {

      System.out.println("Task 2 executed");

      Thread.sleep(2000); // 模拟长时间任务

  } catch (InterruptedException e) {

      System.out.println("Task 2 interrupted");

  }

};

// 第一个任务立即执行,第二个任务延迟1秒执行

scheduledExecutorService.schedule(task1, 0, TimeUnit.SECONDS);

scheduledExecutorService.schedule(task2, 1, TimeUnit.SECONDS);

在这个例子中,task1会立即执行,而task2会在task1执行后的1秒钟开始执行。

尽管使用了单线程池,但任务并非同时执行,而是顺序执行。

2. 任务的重复调度

我们也可能设置了一个任务的定期调度,并且在任务执行过程中,它会被重复调度。在这种情况下,多个任务可能在不同时间点被提交到线程池。

例如:

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

Runnable task1 = () -> {

  try {

      System.out.println("Task 1 executed");

      Thread.sleep(3000); // 模拟任务运行时间

  } catch (InterruptedException e) {

      System.out.println("Task 1 interrupted");

  }

};

scheduledExecutorService.scheduleAtFixedRate(task1, 0, 2, TimeUnit.SECONDS); // 每2秒执行一次

在这个例子中,task1将每 2 秒执行一次。由于任务执行时间较长,下一次执行task1会在前一次执行未完成时启动,但由于是单线程池,它们会排队执行。

3. 任务的实际执行时机

在单线程池中,任务的调度时间与实际执行时间之间可能会有延迟,特别是当任务执行时间较长时。

例如,如果我们有多个任务,它们可能会在不同的时机被提交到线程池,但由于是单线程池,任务会按照顺序执行,尽管它们可能在不同的时间点开始执行。

4. 任务的阻塞和等待

如果我们在任务中使用了阻塞方法(例如Thread.sleep()或Object.wait()),它会导致当前任务阻塞,直到方法执行完成。

在单线程池中,只有一个任务会执行,当这个任务正在等待或者休眠时,其他任务需要等它完成。

例如:

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

Runnable task1 = () -> {

  try {

      System.out.println("Task 1 started");

      Thread.sleep(3000); // 模拟任务执行时长

      System.out.println("Task 1 finished");

  } catch (InterruptedException e) {

      System.out.println("Task 1 interrupted");

  }

};

Runnable task2 = () -> {

  try {

      System.out.println("Task 2 started");

      Thread.sleep(2000); // 模拟任务执行时长

      System.out.println("Task 2 finished");

  } catch (InterruptedException e) {

      System.out.println("Task 2 interrupted");

  }

};

scheduledExecutorService.submit(task1);

scheduledExecutorService.submit(task2);

在这个例子中,task1会先执行,task2必须等到task1执行完成之后才能执行。即使它们的启动时机不同,它们也会在单线程池中依次执行。

好吧,不得不承认,我提交给定时调度器的是任务,而不是线程。

翻看日志再次确认,确实是单线池,不管执行哪个任务都是同一个线程,同一个线程在不同时间节点分别执行了A1任务和B1任务。

单线程池不等于单任务池。这该死的理解力,得抠字眼理解。

最后总结

即使是单线程池,任务也会按照提交顺序依次执行。

可能出现“多个任务在池中执行”的情况,通常是因为任务提交的时机不同、任务的执行时间不一致、或调度策略的原因。

在这种情况下,多个任务可能在不同的时间被触发,并在单个线程上顺序执行,而不是同时执行。

如果我们有定时任务或重复任务调度,也需要特别注意任务执行的间隔和执行时间。

话说,我想要的效果该如何实现呢?那就是取消已经存在的定期调度。

在进入A页面时,看看有没有存在的任务调度,如果有,取消掉,然后再把A1任务加入定时调度器里。B页面同理。问题就是这样解决的。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Ojv3W1VwjLMG6_-CpvRYgOSQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券