JakartaEE サーブレットのスレッドセーフとフィルタ利用時の注意点を徹底解説!初心者でも安心の入門ガイド
生徒
「先生、JakartaEEのサーブレットって複数のユーザーが同時にアクセスしたらどうなるんですか?」
先生
「いい質問ですね。サーブレットはデフォルトでシングルトンとして動作するので、同じインスタンスに複数のスレッドが同時に入ってきます。つまりスレッドセーフを意識しないと予期しない不具合が起きるんです。」
生徒
「なるほど…。じゃあ、フィルタを使う場合も同じように気を付ける必要があるんですか?」
先生
「その通りです。サーブレットもフィルタもライフサイクルが似ているので、共通の注意点があるんです。具体的に見ていきましょう。」
1. JakartaEE サーブレットのスレッドセーフとは?
JakartaEE のサーブレットは、リクエストごとに新しいインスタンスが生成されるわけではなく、通常は一つのサーブレットインスタンスが再利用されます。そのため、同じサーブレットに対して同時に複数のユーザーがアクセスすると、複数のスレッドが並行して実行されることになります。これを「サーブレットのスレッドモデル」と呼びます。
もしサーブレット内でインスタンス変数を不用意に使うと、同時アクセスで変数が上書きされてしまい、データの整合性が失われる危険があります。これが「スレッドセーフでない状態」です。例えばログイン処理やセッション管理をインスタンス変数で持ってしまうと、ユーザーの情報が混ざってしまうケースが発生します。
2. サーブレットでスレッドセーフを確保する方法
サーブレットをスレッドセーフにするためには、次のポイントを守ることが大切です。
- インスタンス変数を使わず、ローカル変数にデータを格納する
- 必要な情報は
HttpServletRequestやHttpSessionに保持する - 共有リソースを操作する場合は
synchronizedブロックなどを用いて排他制御する
例えば次のコードは良くない例です。複数のユーザーが同時にアクセスすると message が上書きされてしまいます。
@WebServlet("/unsafe")
public class UnsafeServlet extends HttpServlet {
private String message; // インスタンス変数に状態を持ってしまっている
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
message = "Hello " + req.getParameter("user");
resp.getWriter().println(message);
}
}
3. フィルタ利用時のスレッドセーフの注意点
JakartaEE の フィルタもサーブレットと同じようにシングルトンで管理されます。つまり、複数のリクエストが同時に同じフィルタインスタンスを通過するため、インスタンス変数の扱いには注意が必要です。
例えばログの記録やリクエストの検証などを行うフィルタはよく使われますが、状態を持たせると予期しない動作が起きます。ログの内容が別のユーザーに混ざることもあり得ます。
@WebFilter("/*")
public class UnsafeFilter implements Filter {
private String user; // 危険なインスタンス変数
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
user = request.getParameter("user");
System.out.println("User: " + user);
chain.doFilter(request, response);
}
}
このコードでは同時リクエスト時に user が上書きされ、ログに誤ったユーザー情報が出力される恐れがあります。解決策としては、必ずローカル変数を使うことが推奨されます。
4. フィルタで安全に状態を扱う方法
フィルタ内で必要な情報は、必ずメソッド内のローカル変数で保持しましょう。次の例のように書けば、同時リクエストでも安全に動作します。
@WebFilter("/*")
public class SafeFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String user = request.getParameter("user"); // ローカル変数で保持
System.out.println("User: " + user);
chain.doFilter(request, response);
}
}
このようにフィルタでスレッドセーフを守ることで、ログや認証処理を安全に行うことができます。
5. サーブレットとフィルタで共通する設計上のポイント
JakartaEE サーブレットとフィルタのスレッドセーフを意識する上で、次のような共通点を押さえておくと安全です。
- インスタンス変数でリクエストごとの状態を持たない
- スレッド間で共有する必要がある場合は適切な排他制御を行う
- スレッドローカル(
ThreadLocal)を使うことで、一時的なスレッドごとのデータ管理も可能 - ステートレスな設計を心掛けると安全で保守性も高くなる
特に初心者は「インスタンス変数を極力使わない」というルールを守るだけでも大きなトラブルを防げます。
6. 実務でよくあるスレッドセーフの落とし穴
実際の開発現場では、便利だからといってサーブレットやフィルタにキャッシュや一時的な値をインスタンス変数として持たせてしまうケースがよくあります。これがパフォーマンスの低下やデータの混在を引き起こす原因になります。
また、認証情報やユーザーIDなどをフィルタのインスタンス変数で持つと、セキュリティ事故につながることがあります。そのため、初心者はまず「状態を持たせない」ことを強く意識しましょう。