Java >> Java チュートリアル >  >> Java

Java スレッド スターベーションとライブロックの例

飢餓 スレッドがリソースへのアクセスを継続的に拒否され、その結果、処理を進めることができない場合に発生します。これは通常、貪欲なスレッドが共有リソースを長時間消費する場合に発生します。これが長時間にわたって発生すると、スレッドが十分な CPU 時間を取得できないか、リソースへのアクセスが十分に行われず、スレッド スタベーションにつながります。 .スレッド スタベーションの考えられる原因の 1 つは、異なるスレッドまたはスレッド グループ間でスレッドの優先度が正しくないことです。

別の考えられる原因は、非終了ループ (無限ループ) の使用、または他のスレッドが必要とする重要なロックを保持している間に、特定のリソースで過度の時間を待機している可能性があります。

スレッドの優先度を変更することは、スレッド スタベーションを引き起こす主な原因であるため、一般的には、スレッドの優先度を変更しないようにすることをお勧めします。スレッドの優先順位を使用してアプリケーションを微調整し始めると、特定のプラットフォームと密接に結び付き、スレッド不足のリスクも生じます。

この例では、合計 5 つのスレッドを作成します。各スレッドには、異なるスレッド優先度が割り当てられます。スレッドを作成して優先順位を割り当てたら、5 つのスレッドすべてを開始します。メイン スレッドでは、5000 ミリ秒または 5 秒間待機し、isActive フラグを false に変更して、すべてのスレッドが while ループを終了し、デッド スレッド状態になるようにします。

Runnable インターフェイスを実装する Worker クラスは、getAndIncrement 操作を実行し、ロックを必要としない AtomicInteger の並行クラスを使用しているにもかかわらず、コードのクリティカル セクションのスレッド ロックをシミュレートするためにミューテックス (オブジェクト) で同期しています。カウンターを使用して、各ワーカー スレッドで実行された作業の頻度を数えて確認できるようにしています。一般的なガイドラインとして、優先度の高いスレッドほど多くの CPU サイクルを取得する必要があるため、優先度の高いスレッドほど値を大きくする必要があります。

注意

Windows は、スレッド フォールバック メカニズムを実装しています。これにより、長い間実行する機会がなかったスレッドに一時的な優先順位が与えられるため、完全な枯渇を達成することはほとんど不可能です。しかし、私が生成した数値から、スレッド 5 に割り当てられている CPU 時間の量に、スレッドの優先度がかなり大きな影響を与えていることがわかります。

スレッド不足の例

package com.avaldes.tutorials;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadStarvationExample {
  private static Object mutex = new Object();
  private static volatile boolean isActive = true;
  
  public static void main(String[] args) {
    Thread t1 = new Thread(new Worker(), "Thread_1_P10");
    Thread t2 = new Thread(new Worker(), "Thread_2_P8");
    Thread t3 = new Thread(new Worker(), "Thread_3_P6");
    Thread t4 = new Thread(new Worker(), "Thread_4_P4");
    Thread t5 = new Thread(new Worker(), "Thread_5_P2");
    
    // Priorities only serve as hints to scheduler, it is up to OS implementation to decide
    t1.setPriority(10);
    t2.setPriority(8);
    t3.setPriority(6);
    t4.setPriority(4);
    t5.setPriority(2);
    
    t1.start();
    t2.start();
    t3.start();   
    t4.start();   
    t5.start();   
    
    //  Make the Main Thread sleep for 5 seconds
    //  then set isActive to false to stop all threads 
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    isActive = false;
    
  }
  
  private static class Worker implements Runnable {
    private AtomicInteger runCount = new AtomicInteger();
    
    public void run() {
      // tight loop using volatile variable as active flag for proper shutdown
      while (isActive) {
        synchronized (mutex) {
          try {
              doWork();
          } catch (Exception e) {
            System.out.format("%s was interrupted...\n", Thread.currentThread().getName());
            e.printStackTrace();
          }
        }
      }
      System.out.format("DONE===> %s: Current runCount is %d...\n", Thread.currentThread().getName(), runCount.get());
    }
    
    private void doWork() {
      System.out.format("%s: Current runCount is %d...\n", Thread.currentThread().getName(), runCount.getAndIncrement());
    }
  }
}

Java スレッド スターベーションの例の出力

Thread_2_P8: Current runCount is 30399...
Thread_2_P8: Current runCount is 30400...
Thread_2_P8: Current runCount is 30401...
Thread_2_P8: Current runCount is 30402...
Thread_2_P8: Current runCount is 30403...
Thread_2_P8: Current runCount is 30404...
Thread_2_P8: Current runCount is 30405...
Thread_2_P8: Current runCount is 30406...
DONE===> Thread_2_P8: Current runCount is 30407...
Thread_5_P2: Current runCount is 545...
Thread_1_P10: Current runCount is 40651...
DONE===> Thread_1_P10: Current runCount is 40652...
DONE===> Thread_5_P1: Current runCount is 546...
Thread_4_P4: Current runCount is 10013...
DONE===> Thread_4_P4: Current runCount is 10014...
Thread_3_P6: Current runCount is 64028...
DONE===> Thread_3_P6: Current runCount is 64029...

スレッド スターベーション実行カウントのカウント分析

DONE===> Thread_1_P10: Current runCount is 40652...
DONE===> Thread_2_P8: Current runCount is 30407...
DONE===> Thread_3_P6: Current runCount is 64029...
DONE===> Thread_4_P4: Current runCount is 10014...
DONE===> Thread_5_P2: Current runCount is 546...

スレッド liveLock は、複数のプロセスが互いにブロックしているという点で、デッドロックによく似た状態です。しかし、ライブロックを使用すると、操作を試行するたびに常に失敗するため、スレッドは処理を進めることができません。すべてのスレッドが Object.wait() を呼び出した場合にも、スレッド ライブロックが発生する可能性があります。 .このプログラムはライブロックされ、他のスレッドが notify() を呼び出すまで続行できません または notifyAll() しかし、他のすべてのスレッドも wait() を呼び出しているため 、どちらの呼び出しも行うことができません。

wait()、notify()、notifyAll() の詳しい使用例については、私のチュートリアル Java スレッドの Wait、Notify、および NotifyAll の例 を参照してください。

ライブロックが発生するもう 1 つの理由は、スレッドが相互に応答するアクションを実行する場合です。 1 つのスレッドがアクションを実行して別のスレッドに応答し、もう 1 つのスレッドもそのブロックされた続行に応答するアクションを実行し、実行された各アクションによって状態が再び待機またはブロックされる場合、これは事実上、次のようなデッドロックに似た状態を引き起こします。スレッドは反応し続けますが、進行することはできません。 Java のドキュメントには、私が伝えたいと思う素敵なイラストがありました。彼らがまだお互いをブロックしているのを見て、アルフォンスは右に移動し、ガストンは左に移動します。彼らはまだお互いをブロックしているので…」、この図の詳細については、Java チュートリアルをご覧ください。 .

最後の理由は、すべてのスレッドが無限ループに陥っている場合にもライブロックが発生する可能性があることです。プログラムはこの状態から抜け出すことができないため、ライブロック状態が発生します。

Java スレッド LiveLock の例

package com.avaldes.tutorials;

import java.util.LinkedList;

public class ThreadLiveLockExample {
  public static void main(String[] args) {
    LinkedList<Equation> queue = new LinkedList<Equation>();
    
    Thread t1 = new Thread(new Reader(queue), "Thread_1_P10");
    Thread t2 = new Thread(new Reader(queue), "Thread_2_P10");
    Thread t3 = new Thread(new Reader(queue), "Thread_3_P10");
    Thread t4 = new Thread(new Reader(queue), "Thread_4_P10");
    Thread t5 = new Thread(new Reader(queue), "Thread_5_P1");
    
    t1.start();
    t2.start();
    t3.start();   
    t4.start();   
    t5.start();   
    
    queue.add(new Equation(100,5));
    queue.add(new Equation(120,6));
    queue.add(new Equation(101,3));
    queue.add(new Equation(1024,62));
    queue.add(new Equation(1892090,53));
    queue.add(new Equation(72,8));
    queue.add(new Equation(198,0));   // Will cause Divide by Zero ArithmeticException !!!
    queue.add(new Equation(123,23));
    queue.add(new Equation(98495,876));
    
  }
  
  private static class Reader implements Runnable {
    LinkedList<Equation> queue = null;
    
    public Reader(LinkedList<Equation> queue) {
      this.queue = queue;
    }
    
    public void run() {
      while (true) {
        synchronized (queue) {
          System.out.format("%s Checking elements in the queue...\n", Thread.currentThread().getName());
          try {
            if (queue.size() > 0) {
              Equation eq = queue.remove(0);
              doWork(eq);
              queue.wait(200);
            }
            Thread.sleep(1000);
            queue.notify();
          } catch (InterruptedException e) {
            System.out.format("%s was interrupted...\n", Thread.currentThread().getName());
            e.printStackTrace();
          }
        }
      }
    }
    
    private void doWork(Equation eq) {
      double val = 0;
      
      try {
        val = (eq.getDividend() / eq.getDivisor());
        System.out.format("%s: Equation %d / %d = %f\n", Thread.currentThread().getName(), eq.getDividend(), eq.getDivisor(), val);
      } catch (ArithmeticException ex) {
        ex.printStackTrace();
        // Try to recover from error --- Incorrect Logic
        // put equation back into queue as the first element
        queue.addFirst(eq);
      }
    }
  }
  
  private static class Equation {
    private int dividend;
    private int divisor;
    
    public Equation(int dividend, int divisor) {
      setDividend(dividend);
      setDivisor(divisor);
    }
    
    public int getDividend() {
      return dividend;
    }
    
    public void setDividend(int dividend) {
      this.dividend = dividend;
    }
    
    public int getDivisor() {
      return divisor;
    }
    
    public void setDivisor(int divisor) {
      this.divisor = divisor;
    }
    
  }
}

Java スレッド LiveLock の例の出力

以下の出力からわかるように、アプリケーションで使用した不適切なロジックが原因で、例外を生成する式を最初の要素としてキューに戻し続けているため、ライブロック状態が発生しています。この時点から、すべてのスレッドが同じエラーで失敗します — LIVELOCK

Thread_1_P10 Checking elements in the queue...
Thread_1_P10: Equation 100 / 5 = 20.000000
Thread_5_P1 Checking elements in the queue...
Thread_5_P1: Equation 120 / 6 = 20.000000
Thread_4_P10 Checking elements in the queue...
Thread_4_P10: Equation 101 / 3 = 33.000000
Thread_3_P10 Checking elements in the queue...
Thread_3_P10: Equation 1024 / 62 = 16.000000
Thread_2_P10 Checking elements in the queue...
Thread_2_P10: Equation 1892090 / 53 = 35699.000000
Thread_1_P10 Checking elements in the queue...
Thread_1_P10: Equation 72 / 8 = 9.000000
Thread_2_P10 Checking elements in the queue...
java.lang.ArithmeticException: / by zero
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.doWork(ThreadLiveLockExample.java:70)
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.run(ThreadLiveLockExample.java:53)
	at java.lang.Thread.run(Thread.java:662)
Thread_3_P10 Checking elements in the queue...
java.lang.ArithmeticException: / by zero
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.doWork(ThreadLiveLockExample.java:70)
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.run(ThreadLiveLockExample.java:53)
	at java.lang.Thread.run(Thread.java:662)
Thread_5_P1 Checking elements in the queue...
java.lang.ArithmeticException: / by zero
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.doWork(ThreadLiveLockExample.java:70)
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.run(ThreadLiveLockExample.java:53)
	at java.lang.Thread.run(Thread.java:662)
Thread_4_P10 Checking elements in the queue...
java.lang.ArithmeticException: / by zero
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.doWork(ThreadLiveLockExample.java:70)
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.run(ThreadLiveLockExample.java:53)
	at java.lang.Thread.run(Thread.java:662)
Thread_5_P1 Checking elements in the queue...
java.lang.ArithmeticException: / by zero
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.doWork(ThreadLiveLockExample.java:70)
	at com.avaldes.tutorials.ThreadLiveLockExample$Worker.run(ThreadLiveLockExample.java:53)
	at java.lang.Thread.run(Thread.java:662)

関連投稿

  • Java スレッド、同時実行、およびマルチスレッドのチュートリアル
    この Java スレッド チュートリアルでは、Java スレッドの基本的な概要を説明し、並行性とマルチスレッドに関するチュートリアル シリーズ全体を紹介します。ここから、スレッドの状態、スレッドの優先順位、スレッドの結合、スレッド グループなど、多くの Java スレッドの概念について学習します。さらに、volatile キーワードの使用方法と、wait、notify、notifyAll の使用例について学習します。
  • Java スレッドの状態 - Java スレッドのライフサイクル
    さまざまなスレッド状態の基本を理解してください。状態遷移図を使用して、Java スレッドのさまざまな状態と、スレッドをある状態から別の状態にジャンプさせるイベントを示します。
  • Java スレッドの作成例
    この投稿では、Java で提供される 2 つのメカニズムを使用して Java スレッドを作成する方法について説明します。つまり、Thread クラスを拡張し、並行プログラミング用の Runnable インターフェイスを実装することです。
  • Java スレッドの優先度の例
    この投稿では、Java におけるスレッドの優先順位について説明します。デフォルトでは、Java スレッドはその親スレッドの優先度 (暗黙的) を継承します。 setPriority() メソッドを使用すると、任意の Java スレッドのスレッド優先度を増減できます。
  • Java ThreadGroup の例
    スレッド管理を支援するために、スレッドを整理して論理グループにグループ化する必要がある場合があります。スレッドを threadGroup に配置することで、プロパティを個別に割り当てるという面倒な作業を行う代わりに、そのグループ内のすべてのスレッドにプロパティをセットとして割り当てることができます。
  • Java スレッドのスリープの例
    特定の期間、現在のスレッドの実行を一時的に中断するために、このメソッドを頻繁に使用しているようです。時間をかけて、このメソッドが実際に何をするかを理解しましょう。
  • Java スレッド結合の例
    Java では、Thread.join() を使用すると、指定されたスレッドが終了するまで現在のスレッドが待機します。このメソッドを使用すると、一方のスレッドが必要な処理 (計算の完了など) を完了するまで、もう一方のスレッドを待機させるような順序を課すことができます。
  • Java スレッドを使用した揮発性キーワードの調査
    フィールドを volatile として宣言すると、JVM は変数の可視性、原子性、および順序付けを保証します。これがないと、データが CPU キャッシュにローカルにキャッシュされる可能性があり、その結果、別のスレッドによる変数への変更が他のすべてのスレッドで認識されず、一貫性のない動作が発生する可能性があります。
  • Java スレッドの Wait、Notify、および NotifyAll の例
    notify() と notifyAll() を使用する目的は、ロックを実行するオブジェクトを介してスレッドが相互に通信できるようにすることです。 wait() メソッドを使用するスレッドは、オブジェクトのロックを所有する必要があります。 wait() が呼び出されると、スレッドはロックを解放し、別のスレッドが notify() または notifyAll() メソッドを呼び出すのを待ちます。
  • VisualVM を使用した Java スレッド デッドロックの例とスレッド ダンプの分析
    デッドロックとは、複数のスレッドが永久にブロックし、他のスレッドが終了するのを待っている状態です。このチュートリアルでは、Java スレッドのデッドロック状態につながる状況と、それらを回避する方法について説明します。さらに、Java VisualVM を使用してデッドロック状態の原因を特定し、分析する方法についても説明します。
  • Java スレッド スターベーションとライブロックの例
    スレッドがリソースへのアクセスを継続的に拒否され、その結果スレッドが進行できなくなると、枯渇が発生します。スレッド liveLock は、複数のプロセスが互いにブロックしているという点で、デッドロックによく似た状態です。しかし、ライブロックを使用すると、操作を試行するたびに必ず失敗するため、スレッドは処理を進めることができません。
  • Java 同期とスレッド セーフティのチュートリアルと例
    Java の多くの強みの 1 つは、最初からサポートされているように、デフォルトでマルチスレッドをサポートしているという事実から来ています。 Java がこれに使用するメカニズムの 1 つは、同期によるものです。 Java で synchronized キーワードを使用すると、共有リソースに同時にアクセスして変更できるスレッドの数を制限しようとします。 Java の同期で使用されるメカニズムは、モニターと呼ばれます。
  • 例を使用してスレッド セーフなシングルトン クラスを作成する
    このチュートリアルでは、スレッドセーフなシングルトン クラスを作成する多くの例を取り上げ、それぞれの欠点について説明し、高速で効率的で同時実行性の高いソリューションを実現するための最良のアプローチについていくつかの推奨事項を示します。
  • Java スレッドと同時ロックの例
    このチュートリアルでは、主に並行ユーティリティの使用と、これらが並行プログラミングを容易にする方法に焦点を当てます。

Java タグ