web-dev-qa-db-ja.com

Java永久に実行する必要があるタスクのエグゼキュータのベストプラクティス

私はJava=プロジェクトで作業しており、複数のタスクを非同期で実行する必要があります。Executorがこれを行うための最良の方法であると確信しているので、自分に慣れています(学ぶために給料がもらえます!)しかし、私がやろうとしていることを達成するための最良の方法は何なのか私にははっきりしません。

議論のために、2つのタスクを実行しているとしましょう。どちらも終了する予定はなく、どちらもアプリケーションの存続期間中実行する必要があります。私は次のようなメインラッパークラスを記述しようとしています:

  • いずれかのタスクが例外をスローすると、ラッパーはそれをキャッチしてタスクを再起動します。
  • いずれかのタスクが完了するまで実行されると、ラッパーはそのタスクを認識して再起動します。

ここで、両方のタスクの実装は、run()のコードを無限ループでラップし、実行が完了することはないことに注意してください。try/ catchブロックを使用して、すべてのランタイム例外を処理する必要があります。ループ。私は確実性の別の層を追加しようとしています。私または私をフォローしている誰かがこれらの保護手段を破ってタスクを停止するような愚かなことをした場合、アプリケーションは適切に反応する必要があります。

私が推奨するよりも経験豊富な人々がこの問題に取り組むためのベストプラクティスはありますか?

FWIW、私はこのテストクラスを打ち上げました:


public class ExecTest {

   private static ExecutorService executor = null;
   private static Future results1 = null;
   private static Future results2 = null;

   public static void main(String[] args) {
      executor = Executors.newFixedThreadPool(2);
      while(true) {
         try {
            checkTasks();
            Thread.sleep(1000);
         }
         catch (Exception e) {
            System.err.println("Caught exception: " + e.getMessage());
         }
      }
   }

   private static void checkTasks() throws Exception{
      if (results1 == null || results1.isDone() || results1.isCancelled()) {
         results1 = executor.submit(new Test1());
      }

      if (results2 == null || results2.isDone() || results2.isCancelled()) {
         results2 = executor.submit(new Test2());
      }
   }
}

class Test1 implements Runnable {
   public void run() {
      while(true) {
         System.out.println("I'm test class 1");
         try {Thread.sleep(1000);} catch (Exception e) {}
      }

   }
}

class Test2 implements Runnable {
   public void run() {
      while(true) {
         System.out.println("I'm test class 2");
         try {Thread.sleep(1000);} catch (Exception e) {}
      }
   }
}

それは私が望むように振舞っていますが、私を驚かすのを待っている落とし穴、非効率、またはまったくの間違った頭の状態があるかどうかはわかりません。 (実際、私がこれに慣れていないとすれば、そこにいるとショックを受けます ではなかった それについて何か間違っている/不可抗力です。)

どんな洞察も歓迎します。

36
BlairHippo

以前のプロジェクトでも同様の状況に直面し、怒っている顧客の前でコードが爆発した後、仲間と2つの大きなセーフガードを追加しました。

  1. 無限ループでは、例外だけでなくエラーもキャッチします。時々例外のないことが起こり、Javaが例外ではなくエラーをスローします。
  2. バックオフスイッチを使用して、問題が発生して回復できない場合でも、別のループを熱心に開始して状況をエスカレーションしないでください。代わりに、状況が正常に戻るまで待ってから、再開する必要があります。

たとえば、データベースがダウンし、ループ中にSQLExceptionがスローされるという状況がありました。不幸な結果は、コードが再びループを通過したこと、同じ例外を再びヒットしたことなどでした。ログには、同じSQLExceptionが1秒間に約300回ヒットしたことが示されています。 ...これは断続的に数回、JVMが5秒ほど停止することがあり、その間アプリケーションが応答しなくなり、最終的にエラーがスローされてスレッドが停止しました。

したがって、以下のコードに大まかに示されているバックオフ戦略を実装しました。例外が回復可能でない場合(または数分以内に回復することを例外とする場合)、操作を再開する前に長時間待機します。

class Test1 implements Runnable {
  public void run() {
    boolean backoff = false;
    while(true) {
      if (backoff) {
        Thread.sleep (TIME_FOR_LONGER_BREAK);
        backoff = false;
      }
      System.out.println("I'm test class 1");
      try {
        // do important stuff here, use database and other critical resources
      }
      catch (SqlException se) {
       // code to delay the next loop
       backoff = true;
      }
      catch (Exception e) {
      }
      catch (Throwable t) {
      }
    }
  }
}

この方法でタスクを実装する場合、checkTasks()メソッドを使用して3番目の「ウォッチドッグ」スレッドを作成しても意味がありません。さらに、上記で概要を説明したのと同じ理由で、エグゼキュータを使用してタスクを再度開始する場合は注意が必要です。最初に、タスクが失敗した理由と、環境が安定した状態であり、タスクを再度実行すると役立つかどうかを理解する必要があります。

31
Yoni

それを目撃する以外に、私は一般的に [〜#〜] pmd [〜#〜] および FindBugs のような静的分析ツールに対してJavaコードを実行してより深い問題のために。

特にこのコードでは、FindBugsは、results1とresults2がレイジーinitで揮発性ではなく、run()メソッドが明示的に処理されていないために例外を無視する可能性があることを好みませんでした。

一般的に、同時実行テストで Thread.sleep を使用することは少し嫌いです。タイマーを優先するか、状態/条件を終了します。 Callableは、結果を計算できない場合に例外をスローする中断が発生した場合に何かを返すのに役立ちます。

いくつかのベストプラクティスと考えるためのより多くの食べ物については、 実践の同時実行性 をチェックしてください。

7
David Sowsy

試しましたか Quartzフレームワーク

これはどう

Runnable task = () -> {
  try{
    // do the task steps here
  } catch (Exception e){
    Thread.sleep (TIME_FOR_LONGER_BREAK);
  }    
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(task,0, 0,TimeUnit.SECONDS);
0
deepak