Javaと
マルチスレッド
目次
1. きっかけ 2. マルチスレッド対応が必要になる場面とは? 3. Javaのプロセスとスレッドについて 4. Javaのメモリ構成について 5. スレッドセーフについて 6. スレッド間競合における問題の回避策あれこれ 7. まとめきっかけ
現場の新人SEより、Webアプリケーションサーバに関して、 以下の質問を受けた。 「ConcurrentModificationExceptionの発生原因について」 質問は、以下のように続いた。 「マルチスレッドで起きる例外のようだが、 シングルスレッドでも起きることがあるのか?」きっかけ
Webアプリケーションサーバは
基本的にマルチスレッド環境なんだけど
そのことを理解できていないのかな?
Webアプリケーションサーバにおける マルチスレッドを考慮したプログラミングの 基礎知識をまとめてみました。目次
1. きっかけ 2. マルチスレッド対応が必要になる場面とは? 3. Javaのプロセスとスレッドについて 4. Javaのメモリ構成について 5. スレッドセーフについて 6. スレッド間競合における問題の回避策あれこれ 7. まとめマルチスレッド対応が必要になる
場面とは?
Javaバッチなどで、明示的に並列処理を行う場合 自分でマルチスレッド処理を組むため、意識できる
Javaアプリケーションサーバ上でのプログラミング (Tomcat, WebSphere, WebLogic, etc.)
アプリケーションサーバ側でマルチスレッド処理部分が行われるため、
基礎知識が不足していれば、自分ではマルチスレッド環境で動く コードだと意識できない場合がある
Javaサーバサイドプログラミングは
すべてマルチスレッドである!
マルチスレッド起動を行うコーディングをしなくても アプリケーションサーバ自身が並列処理をサポートしている Webアプリケーションサーバ サーブレットAへの リクエスト サーブレットAへの リクエスト サーブレットBへの リクエスト サーブレットA サーブレットB サーブレットC スレッド1:サーブレットA スレッド2:サーブレットA スレッド3:サーブレットB常にマルチスレッドを
意識したプログラミング
が必要不可欠!
目次
1. きっかけ 2. マルチスレッド対応が必要になる場面とは? 3. Javaのプロセスとスレッドについて 4. Javaのメモリ構成について 5. スレッドセーフについて 6. スレッド間競合における問題の回避策あれこれ 7. まとめプロセスとは?
OSによって実行される、個別のプログラムのこと。 プロセスごとに、専用のメモリが割り当てられる。 (参考)e-words IT用語辞典 http://e-words.jp/w/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9.html ソフトウェアの世界では、OSからメモリ領域などの割り当てを受けて 処理を実行しているプログラムのことを言う。スレッドとは?
並列処理の際の、最小の実行単位のこと (参考)e-words IT用語辞典 http://e-words.jp/w/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89.html ソフトウェアやプログラミングなどの分野で、並列処理に対応したOS 上でのプログラムの最小の実行単位をスレッドという。プロセスとスレッドの違い
プロセスは、OSから、専用のメモリ空間を与えられて実行さ れるプログラムの単位。 スレッドは並列処理の最小実行単位。 1つのプロセスに、1つのスレッドしかない場合もある (シングルスレッド) 1つのプロセスに、複数のスレッドがある場合もある (マルチスレッド)プロセスとスレッドの概念図
共有メモリ プロセス1 専用メモリ 専用メモリプロセス2 専用メモリプロセス3 ・・・ スレッド1-1 スレッド1-2 スレッド2-1 スレッド3-1 スレッド3-2 ・・・ プロセス1 プロセス2 プロセス3 ・・・ OS 1つのプロセスに複数スレッドがある場合、 スレッド間でメモリは共有される!目次
1. きっかけ 2. マルチスレッド対応が必要になる場面とは? 3. Javaのプロセスとスレッドについて 4. Javaのメモリ構成について 5. スレッドセーフについて 6. スレッド間競合における問題の回避策あれこれ 7. まとめJavaのメモリ構成図
種類1 種類2 格納対象 共有/個別 ヒープ メモリ Java ヒープ※1 ・オブジェクト(インスタンス)・インスタンス変数 共有 メモリ Permanent ヒープ※2 ・クラス、メソッド情報 ・クラス変数・定数 Native メモリ Cヒープ ・JavaVM自身やNativeコード等 スレッドス タック ・各スレッド毎のローカル変数 スレッド 個別メモリJavaのメモリ構成図
前頁の表について補足。今回は共有メモリとスレッド固有メ モリを明示したかっただけなので詳細説明は省く。
※1…Javaヒープは、New領域(Eden, Survivor0, Survivor1)、 およびOld領域に細分化され、世代管理される。
※2…前頁の表はJava1.7以前のもの。Java1.8では
Permanentヒープが廃止され、Metaspeceが新設されたが、 共有メモリとスレッド個別メモリの分類は変わらない。
目次
1. きっかけ 2. マルチスレッド対応が必要になる場面とは? 3. Javaのプロセスとスレッドについて 4. Javaのメモリ構成について 5. スレッドセーフについて 6. スレッド間競合における問題の回避策あれこれ 7. まとめスレッドセーフとは?
マルチスレッド環境において、他のスレッドの影響を受けず に安全な実行が可能なこと (参考)e-words IT用語辞典 http://e-words.jp/w/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%82%BB%E3%83%BC%E3 %83%95.html マルチスレッド環境で、ライブラリやプログラム、クラスなどが複数 のスレッドから同時に利用されても正常に動作すること。スレッドセーフの条件
原則
• 他のスレッドから完全に独立していること具体的には?
• 他のスレッドと共通のオブジェクト(インスタンス)を参照してい ないこと絶対にスレッドセーフなクラス
ローカル変数は使用している インスタンス変数、クラス変数は、状態が不変のもの(定 数)しか参照していない この状態をステートレスという。 ※但し、publicメソッドの引数として渡されるオブジェクトが、 共有オブジェクトであった場合は、スレッドセーフではなくなる。 →厳密にスレッドセーフを意識するなら、引数のオブジェクトを コピーして、そちらを使うなどの考慮が必要?絶対にスレッドセーフなクラス
<サンプルソース1>
public class SampleA extends HttpServlet { private final String HTML =
"<HTML>" + "<BODY>" +
"Hello World !" + "</BODY>" +
"</HTML>" ;
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, java.io.IOException {
PrintWriter out = res.getWriter(); out.println(HTML); } } HttpServletのサブクラス として、SampleAクラス を作成 doGetメソッドを作成 PrintWriterオブジェクトを取 得し、ローカル変数に格納し printlnメソッドでHTML出力 HTMLはインスタ ンス変数だが、 final Stringのため 不変オブジェクト
絶対にスレッドセーフなクラス
<サンプルソース1>
スレッド1用スタック out req res スレッド 1 スレッド2用スタック out req res スレッド 2 共有メモリ領域 PrintWriterオブジェクト HttpServletRequestオブジェクト HttpServletResponseオブジェクト PrintWriterオブジェクト HttpServletRequestオブジェクト HttpServletResponseオブジェクト SampleA オブジェクト HTML (定数) SampleAクラスのdoGetメソッドに複数スレッドから同時に アクセスされた場合状況次第でスレッドセーフなクラス
ローカル変数は使用している クラス変数は、状態が不変のもの(定数)しか 参照していない インスタンス変数は参照・使用しているが、そのクラスのイ ンスタンスは、複数スレッドからアクセスされることがないこ とが明らかである。状況次第でスレッドセーフなクラス
<サンプルソース2 その1>
public class SampleB extends HttpServlet { private final String HTML =
"<HTML><BODY>" + “Name: %s" +
"</BODY></HTML>" ;
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, java.io.IOException {
PrintWriter out = res.getWriter(); SampleBForm form = new SampleBForm(); form.setName(req.getParameter(“NM”)); out.println(String.format(HTML, form.getName())); } } HttpServletのサブクラス として、SampleBクラス を作成 SampleBFormオブジェクトを 作成し、リクエストパラメー タを格納 HTML内に変数埋 め込み箇所を定義 格納したformから値を取り出 し、HTMLに埋め込んで出力
状況次第でスレッドセーフなクラス
<サンプルソース2 その2>
public class SampleBForm {
private String name;
public String getName() { return this.name;
}
public void setName(String name) { return this.name = name;
}
}
1つのインスタンス変数と、 getter, setterメソッドを定義
状況次第でスレッドセーフなクラス
<サンプルソース2 その2>
スレッド1用スタック out req res スレッド1 共有メモリ領域 PrintWriter Obj. HttpServletRequest Obj. HttpServletResponse Obj. SampleB Obj. HTML(定数) SampleBクラスのdoGetメソッドに複数スレッドから同時に アクセスされた場合form SampleBForm Obj.
name SampleBForm Obj. name スレッド2用スタック out req res スレッド2 PrintWriter Obj. HttpServletRequest Obj. HttpServletResponse Obj. form
スレッドセーフではないクラス
複数スレッドから参照されているインスタンスが、インスタ
ンス変数を参照・使用している
または
スレッドセーフではないクラス
<サンプルソース3>
public class SampleC extends HttpServlet { private final String HTML =
"<HTML><BODY>" + “Time: %s" +
"</BODY></HTML>" ;
private static final SimpleDateFormat sdf
= new SimpleDateFormat(“yyyy-MM-dd HH:MM:SS”);
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, java.io.IOException {
PrintWriter out = res.getWriter();
out.println(String.format(HTML, sdf.format(new Date()))); } } HttpServletのサブクラス として、SampleCクラス を作成 クラス変数として SimpleDateFormatを 定義 doGetメソッド内から SimpleDateFormat#format メソッドを呼び出し ※スレッドセーフではない ことで有名
スレッドセーフではないクラス
<サンプルソース3>
スレッド1用スタック out req res スレッド1 共有メモリ領域 PrintWriter Obj. HttpServletRequest Obj. HttpServletResponse Obj. SampleC Obj. HTML(定数) SampleCクラスのdoGetメソッドに複数スレッドから同時に アクセスされた場合 SampleC クラス定義情報 sdf SimpleDateFormat オブジェクト format() スレッド2用スタック out req res スレッド2 PrintWriter Obj. HttpServletRequest Obj. HttpServletResponse Obj.確実にスレッドセーフを担保する
ための基本方針
インスタンスが複数スレッドから参照されるクラスの場合 • ローカル変数は使用可能 • インスタンス変数・クラス変数は、定数の参照のみ可能 インスタンスが複数スレッドから参照されないクラスの場合 • ローカル変数・インスタンス変数は使用可能 • クラス変数は、定数の参照のみ可能JavaEEの主なフレームワークの
マルチスレッド対応
フレーム ワーク Controllerクラス インスタンスの複数ス レッド共有 クラス 変数 インスタンス 変数 JavaEE標準 HttpServlet 有り(基本的に流用) NG NG ApacheStruts Action 有り(Singleton) NG NG Seasar2 SAStruts Action 無し(リクエスト毎) NG OK Spring Web MVC Controller 有り(Singleton) ※設定次第では無し NG NG ※設定次 第ではOK ※Spring Frameworkにおいては、インスタンスの生成スコープを設定変更できるため、呼び出し毎、 またはリクエスト毎にインスタンスを生成する設定にすれば、複数スレッドからの共有はされない。
目次
1. きっかけ 2. マルチスレッド対応が必要になる場面とは? 3. Javaのプロセスとスレッドについて 4. Javaのメモリ構成について 5. スレッドセーフについて 6. スレッド間競合における問題の回避策あれこれ 7. まとめ複数スレッドの競合で正しい処理が
できなくなる典型パターン
read-modify-write: 読み取って変更して書き戻す check-then-act: チェックした結果に基づいて処理 check-if-absent: チェックしてなければ置き換える いずれも、状態を読み取り、その結果に基づき行動するが、 並列処理では、行動する前に他スレッドにより、その行動する 前提の状態が書き換わってしまい、うまくいかなくなる。 (参考) http://jis.hatenablog.com/entry/20100731/1280557752 http://wiki.javazuki.com/concurrent-race-conditionConcurrentModificationException
CollectionクラスをIteratorを使ってループ中に、その Collection自体に対して変更操作を行った際に発生する。 主にマルチスレッド環境で発生する例外だが、 シングルスレッド環境でも、たとえば、ループ内で 自分自身の要素を削除しようとすれば発生する。 回避策としては、Iteratorを使わないという原始的な方針もあ るが、並行Collectionクラスを使う方法が推奨。 ただ、それだけでは根本解決しないことも多いので注意。 (参考) http://futurismo.biz/archives/2811スレッドセーフ基本方針が
満たせない場合の対応方針
複数スレッドからアクセスされる可能性のある箇所のなかで、 共有変数を参照・更新するような、特に注意すべき箇所 に対しては、以下のような対応が有効となる。 1. 複数スレッドが同時にアクセスしないよう、 同期化(Synchronized化)する 2. Atomic型のクラスを利用(java.util.concurrent.atomic) 3. 並行Collectionクラスを利用(java.util.concurrent) 4. スレッド毎に個別のデータを格納できる ThreadLocalクラスを利用同期化(Synchronized化)
前述のような、競合時に問題となる処理に対して、 synchronized化し、一連の処理が分割実行されないようにする。 ただ、並列性が損なわれ、パフォーマンス低下を招く危険が あるため、注意が必要。 Atomic型や、並行Collectionクラスを利用することで 解決する場合はそちらが推奨される。Atomic型
int counter=0; counter++; といった、一見1つの処理のように見えて、実は、①読み取り ②1加算③書き込み、という複合操作になっているような操作 を、Atomicな操作(分割されない最小単位の操作)として実 行させることができるクラス。Java1.5で導入。AtomicInteger counter = new AtomicInteger(0); counter.AddAndGet(1);
並行Collection型
並列なアクセスに対応したCollectionクラス。Java1.5で導入。 先に述べたConcurrentModificationExceptionが発生しない。 ConcurrentHashMap Iteratorループ中に別スレッドが値を更新しても、直前の更新を反 映した値が取得できる(但し確実ではない) CopyOnWriteArrayList Iteratorや、更新処理時、Collectionのコピーを生成し、競合が起き ないように処理する。かなりコストが高いが、更新頻度よりループ 処理などのデータ走査頻度が多い場合にメリットがある。 (参考) http://software.fujitsu.com/jp/technical/interstage/apserver/guide/pdf/ConcurrentMap.pdf http://wiki.javazuki.com/concurrent-race-condition http://d.hatena.ne.jp/j5ik2o/20090813/1250118023ThreadLocal
スレッドごとの固有情報を保存することができ、マルチス レッド環境における競合回避には、非常に便利です。
ThreadLocal<String> str = new ThreadLocal(“abc”); 但し、Webアプリケーションサーバ上で使う場合、メモリ リークが起きる場合があるようです。
もし利用する場合は、確実にThreadLocal#removeするように 心がけることが重要になりそうです。