質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

servlet

Servletとは、Webページの動的な生成やデータ処理などをサーバ上で実行するために、Javaで作成されたプログラムです。 ショッピングサイトやオンラインバンキングといった、動的なウェブサイトの構築に用いられています。

Q&A

2回答

514閲覧

Java マルチスレッドのテスト方法が知りたい

REIA

総合スコア27

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

servlet

Servletとは、Webページの動的な生成やデータ処理などをサーバ上で実行するために、Javaで作成されたプログラムです。 ショッピングサイトやオンラインバンキングといった、動的なウェブサイトの構築に用いられています。

0グッド

0クリップ

投稿2024/11/07 22:47

編集2024/11/08 03:23

背景

JSP, Servletを使用して
WEBサービスを開発しています。
あるFormDataを送ったらDBに登録する処理を行っており
それをstatic変数でsynchronized句で囲っています
その登録処理で主キーの最大値にインクリメントして
登録してるので複数動くと主キーが被りエラーになります。

目的

複数から同時にリクエストが来たときエラーが出ずに登録出来るか確認する
期待値はすべて登録させること

試したこと(検討中含む)

  • サーブレットのインスタンスを生成し

 登録メソッドを呼ぶプログラムをマルチプロセスで動かす
問題:(synchronizedはシングルプロセス上で複数スレッドからの処理の同期を取るためテストにならず)

  • GETリクエストで登録先にリクエストを送るURLを同一Tomcatに配置しそこにマルチプロセスでcurlする

問題 : クライアント -> 登録テスト -> 登録先の順でアクセスし登録テストに同時にたどり着いてもその先でスレッドに分割され優先順位がつけられるので厳密に同時ではない

質問

 上記以外に方法ありますでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2024/11/08 01:51

> 上記以外に方法ありますでしょうか? ご提示の方法では「どのような問題」があるために、別な方法を求めている感じなのでしょうか。
jimbe

2024/11/08 03:01

タイトル、マルチスレッドでなくマルチプロセスのほうが良いのでは。
REIA

2024/11/08 03:25

問題点の追加をしました 検討案1はテストにならなかったのと 検討案2は同時ではなくなるのでは?とお?思っています
dodox86

2024/11/08 06:20

質問自体はマルチスレッド?プロセス?下でのテスト方法に関することでしょうが、そもそもの前提が妙なかんじがしています。 > あるFormDataを送ったらDBに登録する処理を行っており > それをstatic変数でsynchronized句で囲っています > その登録処理で主キーの最大値にインクリメントして > 登録してるので複数動くと主キーが被りエラーになります。 JavaサーブレットでのWEBアプリは、単一のプロセスでリクエスト毎にスレッド化(マルチスレッド)、とのようなかたちが一般的だと認識していますが、synchronizedでアプリ中で適切に排他されているのであれば、競合することはなく、主キーが被るようになることは無いはずと思うのですが違うのでしょうか。 WEBアプリで主キーとなる値を新規に生成している、と言うことなのですよね。この方法自体が少し単純すぎる気がしています。 (単なるテストとか実験的なプロジェクトならありかもしれませんが) DBへのアクセスを挟んでのsynchronizedでの排他は、I/Oを挟んで比較的長時間となるので、もともとの使い方が推奨されたかたちではないような気もします。 RDBMSの製品にもよるでしょうが、SELECT FOR UPDATEのようにDB側で排他をかけるのが一般的な方法のひとつではないでしょうか。 あるいは、生成した主キーが被ったら再生成してリトライするとか。 極端な話、複数のサイトでのホスティングで同じDBに更新かけたら、主キーが被ることがあるのは避けられません。 Javaサーブレットではなく他の同じような形態のWEBアプリや、最近の主流ではどうやっているのか興味がありますね。
xebme

2024/11/08 22:06

複数クライアントから同時リクエストが到達した場合の対応と思われます。 一般にはデータベースのレベルで一意に採番すれば解決する。 Webレベルの直列化にはSynchronizer Tokenパターンが参考になりますが ... 。
REIA

2024/11/10 08:10

> synchronizedでアプリ中で適切に排他されて >いるのであれば、競合することはなく、 >主キーが被るようになることは無いはずと >思うのですが違うのでしょうか 私も同じ認識ですが本当にそうだよね?を確認するためのテストです 私が新規で作ったわけではなく最小限の改修である必要があるため簡単にやり方を変更できないです。。
xebme

2024/11/10 09:58

>主キーが被るようになることは無いはずと >思うのですが違うのでしょうか 主キーをどこで採番していますか。採番SQLが読み/書き2つに分割されていますか。
dodox86

2024/11/10 10:01

> >思うのですが違うのでしょうか > 私も同じ認識ですが本当にそうだよね?を確認するためのテストです なるほど、そうだったのですね。そうであれば納得しました。質問の説明文中、以下の部分 > それをstatic変数でsynchronized句で囲っています > その登録処理で主キーの最大値にインクリメントして > 登録してるので複数動くと主キーが被りエラーになります。 を読んで、今現在エラーが発生することが実際にあったのかなと思いました。
xebme

2024/11/10 10:50 編集

わかりました。 tomocatのインスタンス(プロセス)が一つ(クラスタリングしない)という条件で、 synchronized句でstatic変数はキャッシュからメモリへの書き込みが保証されるので、他のスレッドから更新後の値が見えます。 syncronizedしていなければインクリメントした値がキャッシュに留まり他のスレッドから見えないことが起こりえますが、常に起こるかはわかりません。 マルチコアCPUを使用しているという前提
xebme

2024/11/10 10:42

synchronizedスコープとDBトランザクションのスコープが一致しているか? インクリメントが迂回されていないか? 登録SQLで同時に主キーをインクリメントすべきですね。
guest

回答2

0

確認

念の為確認というか前提と論点整理なのですが

  • アプリケーションサーバ 1 ノード、DBサーバ 1 ノード構成
    (※アプリ&DB同居構成でも同じ)
  • いわゆる連番ID
  • アプリケーションサーバ側でのID採番

という認識でよいでしょうか?

複数動くと主キーが被りエラーになります

とありますが、現状実際にエラーになっているわけではなく「エラーにならない(ような実装が出来ている)ことを確認したい」でいいですよね?

余計な話

正直にいうと特別な理由がない限りはこの構成にしない方がよいことが多いので
Java側のsynchronizedではなくDB側のロック/トランザクションで解決した方がよいのでは?とか
ただのオートインクリメントならばDB側での採番が妥当では?とか
多ノードDBまで見るならばそもそもID体系を見直してどこで採番しても問題ないようにする(UUID/ULID、その他独自実装etc)とか
余裕があれば色々ぐぐったりして調べてみると役に立つかもしれません

本題

マルチスレッド/排他制御のテストは本質的に難しく、「あらゆるシーンで完璧な過不足のないテスト」などは自分も用意できないですが
例えば今回のような場合はこのような検証を行うと思います。

  1. 意図的に競合が起きるように手を加えながら同時実行し、
    クリティカル箇所の処理順序に問題ないかを確認する構造的な検証
  2. 同時アクセスorメソッド呼び出しを多数回実施するやや実践的な検証

マルチプロセス/マルチスレッド

質問文の試したことで『登録メソッドを呼ぶプログラムをマルチプロセスで動かす』とありますが
おそらく質問者さんも気づかれているとおり、これは意味がありません。

こういう場合は「テストドライバとして、『複数スレッドを自前で立ち上げて同時実行させる』ようなコードを書く」が一番手っ取り早いです。
アプリケーションサーバが1ノード前提なので、アプリケーションのプロセスを複数立ち上げてはいけません。
tomcat内部では複数スレッドでそれぞれリクエストを受け付けて同時に当該メソッドを実行することになるので
それを模すために自前で複数スレッド立ち上げるのが必要となります。

そうではなくtomcatを立ち上げたうえで、リクエストを送る方をマルチプロセスで動かすのももちろん効果があります。
質問文の試したことで問題とされている『登録テストに同時にたどり着いてもその先でスレッドに分割され優先順位がつけられるので厳密に同時ではない』というのがちょっとよく分からないのですが、
これは「(最終的なクリティカルセクション到達が)同時かもしれないし同時ではないかもしれないがそれを確認はできないので、ちゃんと検証になっているかどうかも分からない」という意図でしょうか?
それであってるのならば、基本的には「多数回実施して検証とする」が一般的なアプローチです。
「多数回しても十分かどうか分からんやろ!」と言われそうですが、例えば
「わざとsynchronizedを外したうえで同条件で実施してみて、エラーが再現するのに十分かチェックする」などで適切な実施条件を手探りで検討もできます。
try/catchで同時実行エラーかを標準出力しつつ最終的に成功回数/発生した原因ごとエラー回数を見るとかで、どのくらいの時間(回数分)実施したら十分かが分かるでしょう。

①意図的に競合を起こす

まずは、そもそも処理順序や構造として正しいかの検証をします。

例として

  1. 当該メソッドのソースコード書き換え
    1. クリティカルセクションに入った直後にsleep処理を入れる
    2. いろんなタイミングで「スレッド番号+処理内容」をコンソール出力
  2. テストドライバのコード
    1. 当該メソッドを呼び出すようなスレッドを複数生成
    2. 同時にスレッド開始

こんな感じで実行すると、おそらく以下のような出力になるでしょう

  1. ほぼ同時に、クリティカルセクションに入る前の表示がコンソールに出力
  2. 先に入った方のスレッドでsleep開始の表示がコンソールに出力
  3. 先に入った方でsleep終了の表示
  4. 先に入った方で本命の処理が進み、クリティカルセクションを抜けるまでのすべての表示が続く
    1. この間、待っている方は何も処理が進まない
  5. 後に入った方のスレッドでsleep開始の表示がコンソールに出力
  6. (以下略)

ここでクリティカルセクション内の出力が複数スレッドで入り乱れて表示されるようであれば、なんらかの実装ミスや前提条件の勘違いなどがあることになります。

メソッド自体を書き換えて余計な処理を追加するため、これ単体では検証として十分ではないのですが
これが通らないのはそもそもおかしいので、必要条件になります。

※IDEのデバッガを使っても同じことはできると思いますが、出力がないとぱっと見わかりにくいとか予備知識ないと正確な手順が難しいとかで、コードを弄らなくて良い分を差し引いても一長一短ではないかと思っています

②多数回実施

これは前述のスレッドを自前で立ち上げてメソッド呼び出しする方でも、tomcatを立ち上げてリクエストを複数プロセスから投げてservlet経由で実行する方でも、どちらでもよいのですが
開発当初であればメソッド呼び出し、最終的にはservlet経由で検証することになると思います。

こちらは勿論前述のような本体コードへ手を加える必要はありません。

実施条件については「マルチプロセス/マルチスレッド」の所で示した通りですが
基本的には何パターンかでやることになると思います。

servlet経由の方は、どちらかというと非機能要件として性能テストの一貫で実施されるかもしれません。
負荷テストツールなんかを使ってもいいでしょう。

投稿2024/11/09 09:18

pecmm

総合スコア647

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

static変数の更新スコープとDBトランザクションのスコープが一致しているか

質問のコメントから次のような状況を想定してみました。

①でインクリメントが排他的に実行されます。②はマルチスレッドで実行されます。複数スレッドが同じひとつのstatic変数を使用します。先に実行したスレッドは成功しますが、後に実行するスレッドは主キーが重複します。データベースの内容を確認すると、失敗したスレッドの数だけ主キーの値が飛んでいるはず。

java

1synchronized { 2static変数のインクリメント 3} 4static変数を使うSQLの発行

改善案A
①でstatic変数の値をローカル変数にコピーして、②のSQLにはローカル変数の値を使用する。

java

1synchronized { 2static変数のインクリメント 3static変数の値をローカル変数にコピー 4} 5②ローカル変数を使うSQLの発行

改善案B
以下のようにsynchronizedブロックのスコープを拡張する。

java

1synchronized { 2static変数のインクリメント 3static変数を使うSQLの発行 4}

これはデバッグ以前、設計の問題です。その他の問題を挙げておきます。

  • DBトランザクションがロールバックすると欠番が生じる
  • tomcatのクラスタリングに対応できない(主キーの値を同期化したりすると性能劣化、潜在バグの可能性)

「主キーの値は更新SQLと同じトランザクション内でインクリメントする」

テスト

テストは仕様を満たさない場合を検出しましたが、コードをみてもテスト失敗の原因がわからないのだと思います。たとえば上の想定したコード(synchronized{}は構文的に正しくありませんが...)では、//③の箇所に複数スレッドが到達したときに同じstatic変数の値を使う主キー重複エラーが起こります。

java

1synchronized { 2static変数のインクリメント 3} 4//③ 5static変数を使うSQLの発行

原因を調べるためにコードスニペットを切り出してマルチスレッドの動作を確認することをお勧めします(単体レベルのテスト)。マルチスレッドが関係するバグは再現が難しいことがあります。できるだけ小さなコード範囲で動作を調べてください。コードを見てバグを発見できるようになるまで、学習には時間がかかります。

投稿2024/11/10 23:41

編集2024/11/11 22:39
xebme

総合スコア1089

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問