ScheduledExecutorService中的公平性问题

时间:2014-02-12 10:00:07

标签: java scheduledexecutorservice

以下示例显示了ScheduledExecutorService中的问题。我正在安排两个任务“1”和“2”运行超过计划间隔。任务“2”提交另一个任务只执行一次。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestExecutorFairness {
  public static void main(final String[] args) {

    final int interval = 200;
    final int sleeptime = 600;

    final ScheduledExecutorService executor = Executors
        .newSingleThreadScheduledExecutor();

    // schedule task 1
    executor.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(sleeptime);
        } catch (final InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println("1");
      }
    }, interval, interval, TimeUnit.MILLISECONDS);

    // schedule task 2
    executor.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(sleeptime);
        } catch (final InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println("2");

        // submit task 3
        executor.submit(new Runnable() {

          @Override
          public void run() {
            System.out.println("3");
          }
        });
      }
    }, interval, interval, TimeUnit.MILLISECONDS);

  }
}

我期望的输出类似于

1
2
1
2
3

但它没有那样执行。任务“3”延迟很长时间但我需要尽快执行。

有没有办法将此行为更改为更公平?或者有人有更好的解决方案?

1 个答案:

答案 0 :(得分:1)

有趣。这似乎违反直觉,因为ScheduledExecutorService的JvaDoc明确提到

  

使用Executor.execute(java.lang.Runnable)和ExecutorService提交方法提交的命令的调度请求延迟为零

因此可以假设提交这样的命令应该是可行的。但在这种情况下,有一些特点。我无法指出这种行为的确切原因,但它显然与

有关
  • 任务花费的时间超过计划间隔
  • 执行的任务
  • 提交的新任务
  • ScheduledExecutorService内部使用DelayedWorkQueue
  • 这一事实
  • 最重要的是:您使用的是单线程 ScheduledExecutorService

一个相当大的问题可能是这会填满工作队列,并且迟早会导致OutOfMemoryError。这也可以在这个(略微调整的)示例中看到:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestExecutorFairness {
  public static void main(final String[] args) {

    final int interval = 200;
    final int sleeptime = 600;

    final ScheduledExecutorService executor = 
        Executors.newScheduledThreadPool(1);

    final long start = System.currentTimeMillis();

    // schedule task 1
    executor.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(sleeptime);
        } catch (final InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println("1 at "+(System.currentTimeMillis()-start));
      }
    }, interval, interval, TimeUnit.MILLISECONDS);

    // schedule task 2
    executor.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(sleeptime);
        } catch (final InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println("2 at "+(System.currentTimeMillis()-start));


        System.out.println("Submitting 3 to "+executor);
        // submit task 3
        executor.submit(new Runnable() {

          @Override
          public void run() {
              System.out.println("3 at "+(System.currentTimeMillis()-start));
          }
        });
      }
    }, interval, interval, TimeUnit.MILLISECONDS);

  }
}

Executor中“排队任务”的数量不断增加。

在这种情况下的解决方案相当简单:而不是

Executors.newScheduledThreadPool(1)

你可以创建一个

Executors.newScheduledThreadPool(3)

当然,这会改变此示例中的“计时行为”。我必须假设此示例中的Thread.sleep()仅用于模拟不适合此示例代码的复杂计算。但也许只是确保线程数至少为numberOfPeriodicTasks+1也可以应用于您的实际应用程序。