2014-02-12 14 views
1

以下示例顯示了ScheduledExecutorService中的問題。我正在計劃兩個運行時間比計劃時間間隔長的任務「1」和「2」。任務「2」提交另一個任務只執行一次。ScheduledExecutorService中的公平性問題

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

有趣。這似乎有悖常理,因爲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); 

    } 
} 

執行器中「排隊的任務」數量不斷增加。

在這種情況下的解決方法是相當簡單:取而代之的是

Executors.newScheduledThreadPool(1)

你可以創建一個

Executors.newScheduledThreadPool(3)

當然,這改變了「定時行爲」這個例子。我必須假設在這個例子中的Thread.sleep()僅僅是爲了模擬一個複雜的計算,它不適合這個例子代碼。但也許只是確保線程數至少爲​​也可以應用在您的實際應用程序中。

+0

我也考慮過更多的線程,但'Thread.sleep()'是Modbus TCP通信的佔位符。這個庫不是爲多線程而設計的。我也可以分成一個執行者和一個執行者,但是如果任務花費太長時間,那麼這個隊列就會佔用執行者的隊列。 – Stephan

+0

因此,有2個週期性任務可能需要比調度時間間隔更長的時間,並且它們阻止當前線程 - 我無法想象在這種情況下應該如何立即執行第三個任務*。根據當前的描述,我現在看到的唯一解決方案是不安排任務3,但直接在任務2中直接執行相應的「Runnable」 - 但由於這太明顯了,我認爲有一個原因你沒有這樣做...... – Marco13

+0

我在任務2中執行任務3只是爲了模擬以後的調用。但我想直接提交比計劃任務更高的優先級。 – Stephan