Javaでマルチスレッドを処理を安全に記述する方法



0. はじめに

Javaを使用した個人開発にて、Scalaで書かれたライブラリを呼び出す場面があったのですが、
その際にマルチスレッドでの実装をする必要があり、考える機会になったので共有します。

1. 前提

今回の記事では、以下を前提といたします。

ライブラリ側のプロクラムを修正することはできない。



2. 今回触れるパターン

今回は、以下3つのパターンについて扱います。

1. ローカル変数以外が使用されている (インスタンス変数、クラス変数はスレッドアンセーフ)
2. ライブラリの処理結果が標準出力 (System.out) である (System.out はstaticなフィールド)
3. ライブラリが単一のファイルやDBを使用しており、同時に2つ以上の処理を実行すると不整合が発生する

3. 解決策 (私の案)

3.1 パターン1とパターン2について

結論としては、「別プロセスで実行することで使用するメモリ空間を分ける」ことで解決しました。

例えば、以下のようなプログラムをマルチスレッドで呼びたいとします。

この処理は、スタティック変数とインスタンス変数を持っているため、
以下の処理では意図した動作をしません。

そこで、実行プロセスごと分けることで、問題を解消しました。
その実装が以下です。読みやすいように2クラスに分けました。

実行プロセスごと分けることで、疑似的にスレッドセーフな実装になりました。

 

3.2 パターン3について

synchronized ブロックを使用する」ことで解決しました。

例えば、以下のようなプログラムをマルチスレッドで呼びたいとします。

この処理は単一のファイルを使用しているため、
ExecutorService や パターン1,2 での解決方法では、
意図した動作をしません。
例として、ExecutorService を使用した誤った実装を記載します。

そこで、「synchronizedブロック」 を使用することでこの問題を解決しました。
但し、この方法は、性能が悪化しますので、利用に際しては性能要件をよく確認することをおすすめします。

これで、意図した挙動になりました。
何度も言いますが、synchronized ブロック内は1多重での実行となるため、
性能が悪化します。性能要件を確認の上対処方法を検討してください。

 



4.最後に - とても重要なこと

ライブラリを使用する側は、ライブラリ側がどのような実装になっているかを
完璧に把握することはとても難しいですし、時間もかかります。
そこで、「テスト」を実施して処理に問題がないを確認することが重要になってきます。
JUnitを使用した単体テストであれば、 ExecutorService などを使用したテストコードを書くべきです。
また、結合テストでは、ケースによると思いますが、
JUnit や Apache JMeter aを使用したテストコードを記述するべきです。