スレッドセーフではない Java クラスとその注意点
Java開発者ではなじみがあるクラスだと思いますがマルチスレッド環境では注意点が必要なクラスを記載します。
スレッドセーフについて:
https://qiita.com/naoyoshinori/items/507c5c3ea6027033f4bb
本記事では ArrayList
、HashMap
、SimpleDateFormat
について、それぞれのスレッドセーフでない理由と注意すべき使用例を解説します。
1. ArrayList
ArrayList
は内部的に配列を使用しており、サイズ変更や要素の追加・削除時に競合が発生する可能性があります。複数のスレッドが同時に ArrayList
を変更すると、データの不整合や ConcurrentModificationException
が発生することがあります。
解決案
スレッドセーフな CopyOnWriteArrayList
を使用する。
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
List<Integer> safeList = new CopyOnWriteArrayList<>();
2. HashMap
HashMap
は内部で put()
や remove()
を行う際に、バケットの再配置(リハッシュ)が発生することがあります。このとき、複数スレッドが同時に操作すると、データの不整合や無限ループが発生する可能性があります。
解決案
スレッドセーフな ConcurrentHashMap
を使用する。
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<Integer, String> safeMap = new ConcurrentHashMap<>();
3. SimpleDateFormat
SimpleDateFormat
は、parse()
や format()
を実行する際に内部状態を変更します。複数のスレッドが同時に format()
や parse()
を呼び出すと、不正なフォーマットや ParseException
が発生する可能性があります。
注意すべき使用例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatExample {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
try {
Date date = sdf.parse("2024-04-01");
String formattedDate = sdf.format(date);
System.out.println(formattedDate);
} catch (ParseException e) {
e.printStackTrace();
}
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
}
}
このコードでは、複数のスレッドが SimpleDateFormat
の parse()
メソッドを同時に呼び出すため、データの競合が発生する可能性があります。
解決案
-
DateTimeFormatter
を使用する
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse("2024-04-01", formatter);
まとめ
クラス | 発生可能性がある場面 | 解決策一例 |
---|---|---|
ArrayList |
要素の追加・削除時にデータ競合が発生 |
CopyOnWriteArrayList を使用 |
HashMap |
同時書き込みでデータ競合が発生 |
ConcurrentHashMap を使用 |
SimpleDateFormat |
format() や parse() の内部状態が変更される |
DateTimeFormatter を使用 |
今回は一例になりますがスレッドセーフの問題がある場合は性能などの検討も行い設計を確認したうえで適切な代替手段を選択をしましょう。