1. 導入:なぜこの知識が重要なのか
Javaで正規表現を扱う際、何気なくコードを書いていませんか?実は、正規表現クラスである「Pattern」と「Matcher」には、マルチスレッド環境における安全性に大きな違いがあります。この違いを理解していないと、並列処理を行うプログラムで「予期せぬ挙動」や「データ破壊」を引き起こす原因になります。本記事では、安全な並列処理を実現するための正しい正規表現の扱い方を解説します。
2. 基礎知識:PatternとMatcherの違い
Javaのjava.util.regexパッケージには、主に2つのクラスが登場します。
Patternクラスは、コンパイル済みの正規表現パターンを保持する「設計図」のようなものです。一度作成すれば、複数のスレッドから共有しても安全(スレッドセーフ)です。
Matcherクラスは、その設計図を元に、特定の文字列を検索・置換する「作業員」です。Matcherは内部に検索位置(インデックス)などの状態を保持しているため、複数のスレッドで共有することはできません(非スレッドセーフ)。
3. 実装/解決策:正しく使い分けるための原則
最も重要な原則は、「Patternはstaticな定数として保持し、Matcherはメソッド内で毎回生成する」ということです。
Patternインスタンスは生成コストが高いため、何度も生成するのはパフォーマンスの無駄です。一方で、Matcherは生成コストが非常に低いため、必要な時にその都度生成するのがJavaの標準的な作法です。
4. サンプルプログラム
以下のコードは、スレッドセーフを考慮した正規表現の利用例です。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
// Patternは不変(Immutable)かつスレッドセーフなので、
// static finalな定数として定義するのがベストプラクティスです。
private static final Pattern EMAIL_PATTERN = Pattern.compile("(?[a-z]+)@(?[a-z]+)");
public void validateEmail(String input) {
// Matcherはメソッド内で生成し、そのメソッド内でのみ使用します。
// これにより、他のスレッドと状態が干渉することはありません。
Matcher matcher = EMAIL_PATTERN.matcher(input);
if (matcher.matches()) {
// 名前付きグループ(Named groups)を使用して値を取得
String name = matcher.group("name");
String domain = matcher.group("domain");
System.out.println("名前: " + name + ", ドメイン: " + domain);
} else {
System.out.println("形式が不正です");
}
}
}
5. 応用・注意点:現場で陥りやすい罠
現場でよく見かけるバグとして、Matcherをクラスの「フィールド変数」にしてしまうミスがあります。
注意点:
Matcherをクラスのフィールド(メンバ変数)にすると、複数のスレッドから同時にそのインスタンスへアクセスが行われ、検索位置がずれたり、例外が発生したりします。
また、正規表現で「名前付きグループ」を使用する場合、グループ名が長くなるとコードが読みづらくなることがあります。その際は、定数でキー名を管理するなど工夫しましょう。
もし、「どうしてもMatcherを共有したい」という特殊な状況がある場合は、ThreadLocalを使用するか、同期処理(synchronized)を検討する必要がありますが、基本的には「メソッド内で都度生成」が最もシンプルでパフォーマンスも高い解決策です。まずはこの基本を徹底してください。

コメント