LoginSignup
0
1

スレッドセーフメモ

Last updated at Posted at 2023-05-31

スレッドセーフについてメモを残す。

HashMap→ConcurrentHashMap

javadocの記述をそのまま抜き出すと以下のようになる。

この実装はsynchronizedされません。複数のスレッドが並行してハッシュ・マップにアクセスし、それらのスレッドの少なくとも1つが構造的にマップを変更する場合は、外部でその同期をとる必要があります。構造的な変更とは、1つ以上のマッピングを追加または削除するオペレーションのことです。すでにインスタンスに格納されているキーに関連付けられた値を変更することは構造的な変更ではありません。これは通常、マップを自然にカプセル化する一部のオブジェクトでsynchronizedすることによって達成されます。そのようなオブジェクトが存在しない場合は、Collections.synchronizedMapメソッドを使用してマップを「ラップ」することをお薦めします。マップが誤ってsynchronizedなしでアクセスされるのを防ぐために、作成時に行うことをお薦めします。
Map m = Collections.synchronizedMap(new HashMap(...));
このクラスの「コレクション・ビュー・メソッド」によって返されるイテレータはフェイルファストです。イテレータの作成後に、イテレータ自体のremoveメソッド以外の方法でマップが構造的に変更されると、イテレータはConcurrentModificationExceptionをスローします。このように、並行して変更が行われると、イテレータは、将来の予測できない時点において予測できない動作が発生する危険を回避するために、ただちにかつ手際よく例外をスローします。
通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、イテレータのフェイルファストの動作を保証することはできません。フェイルファスト・イテレータは最善努力原則に基づき、ConcurrentModificationExceptionをスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。イテレータのフェイルファストの動作はバグを検出するためにのみ使用すべきです。

スレッドセーフなものはConcurrentHashMapなので、そちらを使うように修正する。
当たり前だが、LinkedHashMapも同様にスレッドセーフではないので、同じような修正が必要。
TreeMapも同様。

SimpleDataFormat→FastDateFormat

javadocの記述をそのまま抜き出すと以下のようになる。

同期
日付フォーマットは同期化されません。スレッドごとに別のフォーマット・インスタンスを作成することをお薦めします。複数のスレッドがフォーマットに並行してアクセスする場合は、外部的に同期化する必要があります。

SimpleDataFormatについて少なくともインスタンスごとに作らないとちゃんと同期されません。
FastDateFormatを使うように修正する。

StringBuilder→StringBuffer

javadocの記述をそのまま抜き出すと以下のようになる。

文字の可変シーケンスです。このクラスは、StringBufferと互換性があるAPIを提供しますが、同期化は保証されません。このクラスは、文字列バッファが単一のスレッド(一般的なケース)により使用されていた場合のStringBufferの簡単な代替として使用されるよう設計されています。このクラスは、ほとんどの実装で高速に実行されるので、可能な場合は、StringBufferよりも優先して使用することをお薦めします。
(中略)
StringBuilderのインスタンスは、複数のスレッドで使用するには安全ではありません。このような同期が必要な場合は、StringBufferを使用することをお薦めします。

マルチスレッドな設計の場合は、StringBufferを使うように修正する。

ArrayList→CopyOnWriteArrayList

ArrayListもスレッドセーフではない。

javadocの記述をそのまま抜き出すと以下のようになる。

この実装はsynchronizedされません。複数のスレッドが並行してArrayListインスタンスにアクセスし、それらのスレッドの少なくとも1つがリストを構造的に変更する場合は、外部でその同期をとる必要があります。構造的な変更とは、1つ以上の要素を追加または削除したり、基になる配列のサイズを明示的に変更したりする処理のことです。要素の値だけを設定する処理は、構造的な変更ではありません。これは通常、リストを自然にカプセル化する一部のオブジェクトでsynchronizedすることによって達成されます。そのようなオブジェクトが存在しない場合は、Collections.synchronizedListメソッドを使用してリストを「ラップ」するようにしてください。リストが誤ってsynchronizedなしでアクセスされるのを防ぐために、作成時に行うことをお薦めします。
List list = Collections.synchronizedList(new ArrayList(...));
このクラスのiteratorおよびlistIteratorメソッドによって返されるイテレータは、フェイルファストです。イテレータの作成後に、イテレータ自体のremoveまたはaddメソッド以外の方法でリストが構造的に変更されると、イテレータはConcurrentModificationExceptionをスローします。このように、並行して変更が行われると、イテレータは、将来の予測できない時点において予測できない動作が発生する危険を回避するために、ただちにかつ手際よく例外をスローします。
通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、イテレータのフェイルファストの動作を保証することはできません。フェイルファスト・イテレータは、ベスト・エフォート・ベースでConcurrentModificationExceptionをスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。イテレータのフェイルファストの動作はバグを検出するためにのみ使用すべきです。

マルチスレッドな設計の場合は、CopyOnWriteArrayListを使うように修正する。

同期化

メソッドにsyncronizedをつけて同期化させることが必要になる。
こうすることで複数スレッドで呼び出されたとしても、そのメソッドは1スレッドだけしか一度に処理ができない。
つまり、そのメソッドに関してはマルチスレッドではなくシングルスレッドとなる。

もし、元々そこをマルチスレッドで考えて後から同期化させないとダメとなった場合はパフォーマンスに影響する。
その部分を同期させるための待ち時間が発生するので、その分パフォーマンスが落ちます。

使いどころを間違えると再現の難しいバグ(マルチスレッド問題によるバグとか)、致命的なバグ(デッドロックとか)が発生して
地獄を見ることになるので極力使わない設計にする方が良い。

不変オブジェクト

インスタンスを作成したクラスが後から上書きされない保証のあるオブジェクト。
要は定数。(クラスをこの表現でいいかは微妙だけど)

これはマルチスレッドで使おうが、必ず宣言された値から変更されない。
以下のようなものがある。

  • Stringなどのプリミティブ型
  • Pathクラス
  • LocalDateTime

零に出したStringのjavaDocにも記載がある。

文字列は定数です。この値を作成したあとに変更はできません。文字列バッファは可変文字列をサポートします。文字列オブジェクトは不変であるため、共用することができます。

結論:JavaDoc確認しよう

これはスレッドセーフなクラスだよ、などはjavaDocに記載されている。
少なくともそれらしい記述があるかは確実に確認したほうが良い。

主要なクラスは確認しよう。

実装が見たければ、classファイルを逆コンパイルして確認しよう。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1