技術・開発

【Java】Struts開発の落とし穴!Actionクラスでのスレッドセーフに関する注意点

2012年11月15日

Webアプリケーションを開発する際、マルチスレッド環境において必ず考えなければならない重要な概念の一つが「スレッドセーフ(Thread-safe)」です。

スレッドセーフとは
マルチスレッド環境で、ライブラリやプログラム、クラスなどが複数のスレッドから同時に利用されても正常に動作すること。
(出典:IT用語辞典 e-Words

「自分はしっかり考えて実装しているつもりだ」と思っていても、ふとした瞬間にうっかりミスをしてしまうことがあるんですよね……。
特にJavaのStrutsフレームワークは、複数のユーザーからのリクエストをマルチスレッドで処理して動いているため、スレッドセーフを強く意識しないと重大な不具合(他のユーザーの情報が混ざるなど)に陥る危険性があります。

今回は、一見すると判断がつきにくい「スレッドセーフかどうか」の違いをコード例でまとめました。

【安全】メソッド内で定義する(ローカル変数)

メソッドの「中」で変数を定義した場合、それはスレッドセーフになります。

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // メソッド内で定義したローカル変数はスレッドセーフ
    String test = null;
    test = "test";
    
    return mapping.findForward("success");
}

Javaでは、メソッドが呼び出されるたびにスレッドごとに専用のメモリ領域(スタック)が確保されます。そのため、ローカル変数であれば他のスレッドから干渉されることはなく安全です。

【危険】メソッドの外で定義する(メンバ変数・クラス変数)

一方で、メソッドの「外」で定義した変数(フィールド)にアクセスする場合は、スレッドセーフではありません。

// メソッドの外で定義したメンバ変数(またはstatic変数)
private String test = null;

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 複数のスレッドから同時に書き換えられる危険性あり!
    test = "test";
    
    return mapping.findForward("success");
}

なぜ危険なのか? Struts 1の Action クラスは、「シングルトン(システム全体でインスタンスが1つしか生成されない)」という設計になっています。 つまり、1つの Action クラスのインスタンスを、全ユーザー(全スレッド)で使い回している状態です。そのため、クラスのメンバ変数に値を保持してしまうと、Aさんの処理中にBさんのリクエストが割り込んで変数を上書きしてしまう……といった大事故に繋がります。

既存クラスを使う場合も注意が必要

自分で作成する変数だけでなく、Javaの標準クラスを利用する際にも「そのクラスがスレッドセーフかどうか」の判断が必要です。

よくある例が文字列操作のクラスです。

  • StringBuffer:スレッドセーフ(同期化されているためマルチスレッド環境でも安全だが、少し処理が重い)
  • StringBuilder:スレッドセーフではない(同期化されていないため高速だが、マルチスレッドでの共有には不向き)

まとめ

StrutsでActionクラスを作成する際の鉄則は、「状態を持つ変数はすべてローカル変数としてメソッド内で定義する」ことです。 メンバ変数やstatic変数を使うと、思わぬタイミングでデータが混線するバグを生み出してしまいます。

開発現場でもつい見落としがちなポイントなので、Strutsを触る方はぜひ気をつけてください。

-技術・開発
-, , , , , ,