このページで解決しない高度なJavaのシステム障害はJaTSにお任せください

Trouble 12: マルチスレッド処理で問題がおきます



<Q12-1>lockオブジェクトで待機中のスレッドを再開するために、lockに対してnotify()しても再開する場合と停止したままの場合があります。


<発生環境>
OS RedHatLinux6.1/6.2
JDK JDK1.3
Vender Sun
<A12-1>
1つのlockオブジェクトに対して、複数のThreadがwait()していることが原因です。
lockで一度に待機するThreadを1つにすることで解決できます。
また、lockに複数のThreadが待機している場合、lock.notify()で処理を再開するThreadは任意となることに注意してください。
Page Top

<Q12-2>PIPEを使用してプロセスの起動確認/排他起動を行うCのプログラムを、JNIを使用して複数スレッドからCallすると、通常は一つしか起動されないCのプログラムが複数起動してしまいます。


<発生環境>
OS Solaris2.6
JDK JRE1.3
Vender Sun
<A12-2>
JNIを使用するラッピング用クラスのメソッドをCallするクラスを新たに作成し、ラッピング用クラスを更にラッピングする形で、各メソッドを非staticかつsynchronizedに設定することで解決できます。
上記のクラスのインスタンスをmainスレッド上でstaticに生成し、各スレッドがこのstaticに生成されたクラスのメソッドを介してJNIにアクセスしてください。
Page Top

<Q12-3>Jakarta Commons HttpClientをマルチスレッド環境で利用時に想定よりもパフォーマンスが向上しません。

Jakarta Commons HttpClientをマルチスレッド環境で利用時に想定よりもパフォーマンスが向上しません。
※Java SE 5 で動作確認を行いました。

<発生環境>
OS Windows XP
JDK any
<A12-3>
HttpClientをマルチスレッドで効率よく利用するには、実は、
一般に知られている実装方法とはことなる実装に切り替える必要があります。

Webや雑誌などのチュートリアル記事などで広く紹介されているHttpClientの利用方法
(以下、デフォルト版) は、シングルスレッド環境を想定した利用方法になっています。


マルチスレッド環境に適した方法でHttpClientを利用をするためには、
次のような実装 (以下、マルチスレッド版) にする必要があります。

1.接続マネージャをデフォルト版からマルチスレッド版のMultiThreadedHttpConnectionManagerに変更する。

2.クライアントインスタンスを複数スレッドで共有する。

3.同一ホストへの同時接続数をデフォルト値(2)から変更する。

当社の検証環境では、マルチスレッド版では、デフォルト版の2倍以上のスループットが計測されました。

参考)
・HTTPClientのコネクションプーリングは、接続するHTTPサーバがkeep-aliveに
  対応していることが前提となっています。

・デフォルト版のコネクションプーリングでは、直前の1コネクションのみを
  プーリングする実装になっています。
  このため、複数のホストに交互に接続する利用方法では、
  コネクションプーリングが有効に働きません。

 (例)
   1.ホストAへリクエスト送信
   2.ホストBへリクエスト送信
   3.ホストAへリクエスト送信 ...
  そこで、複数のホストへの接続を行うアプリケーションにおいて、
  コネクションプーリングを有効活用するためには、シングルスレッドであっても
  マルチスレッド版と同じ実装をする必要があります。


 この記事の詳細な情報をJTSメールマガジンにて配信しています。
 情報を取得したい方は、メールマガジンのバックナンバーを参照して下さい。

Page Top

<Q12-4>一定周期での処理を行うプログラムを作成したのですが、指定周期どおりに動いてくれません。

ScheduledExecutorServiceを利用して、一定周期での処理を行うプログラムを作成しましたが、
思い通り動いてくれません。

10秒毎にTaskを実行するプログラムを作成したところ、
以下の通り、開始時間が徐々にずれていってしまいます。

Task0 の2回目の実行開始時刻は、「11010」程度にしたいです。

Task0 Start     (1014)
Task0 End       (1514)
Task1 Start     (1514)
Task1 End       (2029)
Task2 Start     (2029)
Task2 End       (2529)
Task0 Start     (11513)
Task0 End       (12013)
Task1 Start     (12029)
Task1 End       (12543)
Task2 Start     (12543)
Task2 End       (13044)

<発生環境>
OS Any
JDK JDK5.0以降
Vender Sun
<A12-4>
利用するAPIが間違えています。
ScheduledExecutorServiceクラスには、scheduleWithFixedDelayscheduleAtFixedRateという2つのメソッドが用意されています。
この2つのメソッドは図のように動作が異なります。

[scheduleWithFixedDelay]
scheduleWithFixedDelay


[scheduleAtFixedRate]
scheduleAtFixedRate

scheduleAtFixedDelayは、処理間の休止期間を固定にしたい場合に利用し、
scheduleAtFixedRateは、処理開始時間の間隔を固定したい場合に利用します。


今回の目的であれば、scheduleAtFixedDelayではなくscheduleAtFixedRateを使います。
使うメソッドを変更した結果、以下のように動作をするようになりました。
Task0 Start     (1014)
Task0 End       (1514)
Task1 Start     (1514)
Task1 End       (2029)
Task2 Start     (2029)
Task2 End       (2529)
Task0 Start     (11018)
Task0 End       (11518)
Task1 Start     (11518)
Task1 End       (12033)
Task2 Start     (12033)
Task2 End       (12533)
参考:
今回検証に利用したサンプルコードを以下に掲載します。
ScheduledExecutorService service
             = Executors.newSingleThreadScheduledExecutor();

long start = System.currentTimeMillis();
for (int index = 0; index < 3; index++){
  Task task = new Task(index, start);
  service.scheduleAtFixedRate(task, 
              1000, 10000, TimeUnit.MILLISECONDS);
}
public class Task implements Runnable{
 int index_;
 long start_;

 public Task(int index, long start){
  this.index_ = index;
  this.start_ = start;
 }

 @Override
 public void run(){
  System.out.print("Task"
                       + this.index_ + " Start     ");
  System.out.println("(" 
             + (System.currentTimeMillis() - this.start_) + ")");
  
  try{
   Thread.sleep(500);
  }catch (InterruptedException ignore){
   ignore.printStackTrace();
  }
  
  System.out.print("Task" + this.index_ + " End       ");
  System.out.println("(" 
              + (System.currentTimeMillis() - this.start_) + ")");
 }
}

Page Top

<Q12-5>Concurrent APIを利用して、スレッドプールを実装しましたが、並列に動いてくれません。

JDK1.5以降で導入されたConcurrent APIを利用して、スレッドプールを実装し、
3つの処理を並列処理させようとしましたが、
以下のように、それぞれのタスクが順次に実行されます。

Task0-0 Start (31)
Task0-1 Start (1046)
Task0-2 Start (2060)
Task1-0 Start (3075)
Task1-1 Start (4090)
Task1-2 Start (5105)
Task2-0 Start (6120)
Task2-1 Start (7134)
Task2-2 Start (8149)

<発生環境>
OS Any
JDK JDK5.0以降
Vender Sun
<A12-5>
利用するAPIが間違えている可能性が高いです。
ExecutorsクラスのnewSingleThreadExecutorメソッドを利用して
ExecutorServiceクラスを作成すると、
単一スレッドでタスクを実行することになります。

複数スレッドでタスクを並列実行する場合には、
同時実行可能なスレッド数を指定するnewFixedThreadPoolメソッドや、
一定数のスレッドを再利用しながら処理するnewCachedThreadPoolメソッドを利用してください。

[newSingleThreadExecutor]
newSingleThreadExecutor


[newFixedThreadPool]
newFixedThreadPool

使うメソッドを変更した結果、以下のように動作をするようになりました。
Task0-0 Start (203)
Task1-0 Start (203)
Task2-0 Start (204)
Task1-1 Start (1218)
Task0-1 Start (1218)
Task2-1 Start (1219)
Task1-2 Start (2233)
Task0-2 Start (2233)
Task2-2 Start (2234)
参考:
今回検証に利用したサンプルコードを以下に掲載します。
long start = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int index = 0; index < 3; index++){
  executor.execute(new Task(index, start));
}
executor.shutdown();
public class Task implements Runnable{
  int  index_;
  long start_;

  public Task(int index, long start)  {
    this.index_ = index;
    this.start_ = start;
  }

  public void run(){
    for (int index = 0; index < 3; index++){
      long time = System.currentTimeMillis() - this.start_;
      System.out.println("Task" + this.index_ 
                   + "-" + index + " Start (" + time + ")");
      try{
        Thread.sleep(1000);
      }catch (InterruptedException ignore){
        ignore.printStackTrace();
      }
    }
  }
}

Page Top

<Q12-6>ブロック処理に対応したQueueを利用していますが、意図通り動いてくれません。

JDK1.5以降で導入されたLinkedBlockingQueueを用いて、
以下のような処理を実行したいと考えましたが、
処理を待ち受けてくれません。

実行結果のように、Serverが処理を待ち受けずにnullを取得してしまいます。

LinkedBlockingQueueを用いた処理

[実行結果]

Server handles null
Client requests [ Request No. 0 ]
Client requests [ Request No. 1 ]
Client requests [ Request No. 2 ]
Client requests [ Request No. 3 ]
Server handles  [ Request No. 0 ]
(略)
Client requests [ Request No. 410 ]
Server handles  [ Request No. 408 ]
Client requests [ Request No. 411 ]
Server handles  [ Request No. 409 ]
Server handles  [ Request No. 410 ]
Server handles  [ Request No. 411 ]
Server handles  null
Client requests [ Request No. 412 ]

[サンプルコード]
RequestQueue queue = new RequestQueue();
new ClientThread(queue).start();
new ServerThread(queue).start();
public class RequestQueue{
  private BlockingQueue queue_ = new LinkedBlockingQueue();

  public Request getRequest(){
    Request req = queue_.poll();
    return req;
  }

  public void putRequest(Request request){
    queue_.offer(request);
  }
}
※ServerThread、ClientThreadはそれぞれ、
 RequestQueueのgetRequest()、putRequest()をランダム間隔で呼び続けるThreadクラス。

<発生環境>
OS Any
JDK JDK5.0以降
Vender Sun
<A12-6>
ブロックされないメソッドを利用しています。

LinkedBlockingQueueは、ブロック処理を考慮したQueueクラスですが、
ブロック処理が有効なのは、BlockingQueueインタフェースで定義された以下の2つのメソッドのみです。

・take():キューの先頭から要素を取り出す
・put():キューの末尾に要素を追加する


そのため、キューの先頭を取得および削除するpollメソッドや、
指定された要素を追加するofferメソッドを利用して、
LinkedBlockingQueueオブジェクトにアクセスしても、
ブロックされません。

上記のサンプルコードの場合、RequestQueueクラスを以下のように修正すれば、
この問題を解決することができます。
public class RequestQueue{
  private BlockingQueue queue_ = new LinkedBlockingQueue();

  public Request getRequest(){
    Request req = null;
    try{
      req = queue_.take();
    }catch (InterruptedException ignore){
      ignore.printStackTrace();
    }
    return req;
  }

  public void putRequest(Request request){
    try{
      queue_.put(request);
    }catch (InterruptedException ignore){
      ignore.printStackTrace();
    }
  }
}
この修正の結果、実行結果は以下のようになりました。
待ち受け処理が成功していることが分かります。

[実行結果]
Client requests [ Request No. 0 ]
Server handles  [ Request No. 0 ]
Client requests [ Request No. 1 ]
Client requests [ Request No. 2 ]
Client requests [ Request No. 3 ]
Server handles  [ Request No. 1 ]
(略)
Server handles  [ Request No. 430 ]
Client requests [ Request No. 432 ]
Server handles  [ Request No. 431 ]
Client requests [ Request No. 433 ]
Server handles  [ Request No. 432 ]
Client requests [ Request No. 434 ]

Page Top

注意:本文書の内容に誤りがあり、またこの文書によって不利益を被っても、
Acroquest Technology 株式会社は一切関知いたしません。

  • 現象別Index
  • 原因別Index

Find Bugsバグ詳細

Find Bugs Bug Descriptions日本語版

RSSで更新情報を取得する

RSSとは、ホームページの更新情報を配信する為のフォーマットです。
RSSを利用すると、登録したページの情報が更新された場合に、更新情報を自動的に受け取る事ができます。

詳細

弊社小森が執筆致しました

Javaでオブジェクト指向開発

Javaプログラミング言語習得において、新人プログラマーの最初の障害は「オブジェクト指向の壁」です。
本書は、Javaのソフトウェア開発を中心に事業を発展させてきたAcroquest社の新人教育セミナーを加筆・書籍化したもので、大卒の新人に対して、ゼロからJava言語を教えてきた実績をフィードバックしています。

メールマガジン配信中

Javaトラブルシューティングのメルマガをはじめました!是非ご購読ください

詳細