超初心者の方のためのウェブアプリとマルチスレッド

数年前の私の話です。以下のような話を耳にします。

マルチスレッドにすると、スレッドセーフにプログラムを作るのがめんどくさいんだよね−。
で、日和ってsynchronizedとかつけて同期を取ると、パフォーマンスが落ちるんだよね−。

何を言っているのか、さっぱり分かりませんでした。
というのは、私はJavaベースのウェブアプリを作ったことがなかったからです。簡単に言うと、何も知らない素人であったということです。

といって、私は素人から毛くらいは生えたかというレベルですので、理解に誤りなどもあるかもしれません。ということで、少しまとめてみます。

マルチスレッド

マルチスレッドは非常に役に立つ処理です。

あなたが、ウェブアプリなどを作っているとしましょう。何かブラウザからのリクエストを受け付けて、例えばデータベースの値を取ってくるものです。その時、昔ながらのCGIなどは、リクエストのたびに新たなプロセスを立ち上げておりました。
これはこれでよい面もあるのですが、「めちゃくちゃ遅い」という問題点があります。例えば初期化処理を毎回やらねばなりません。

従って、例えばJava Servletを使う際には「アクセスが来るたびにJavaを起動してクラスをロードして…」などということは行いません。既にロードされたクラスが存在し、そのクラスのインスタンスを共用することが可能です。
Servletクラスですが、「インスタンス自体を共用」します。つまり、リクエストが来るたびに新たなServletインスタンスを作るわけではありません。その他、Struts 1.xのActionなどもそうです*1。このような「インスタンスは一つしか作らないよ」というものをシングルトンと呼びます。
シングルトンであるということは、いちいち実行のたびにインスタンスを作らなくてもよいということです。つまり、高速になります。
その代わり、実行のたびに、別のスレッドがそのインスタンスにアクセスしてくるのです。

もちろん、そのリクエストが来た内部の処理において自分でnewすれば、それはリクエストごとにインスタンスを作っているわけですから、シングルトンではありません。(当たり前ですが。)

で、業務で使うウェブアプリケーションにおいて、「シングルスレッドで使う」ということはまずあり得ません。

マルチスレッドとスレッドセーフ

Struts 1.xを使っており、Actionをコントローラとして、HTTPのリクエストの受付を行うとしましょう。

普通はやらないと思いますが、次のような処理を書くとうまく動きません。

public class ABCAction extends Action{
   private ActionForm form; // インスタンス変数!
   public ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response){
      this.form = form;
      ... // this.form を利用した処理。
      return ...;
   }
}

なぜかといえば、この変数formは、「全てのリクエストでシェアされてしまうから」です。

Javaには大きく三つの変数があります。

マルチスレッドで実行している時、例えばあるスレッドでクラス変数をいじると、他のスレッドでも変更されます。インスタンス変数の場合には、スレッドごとに別のインスタンスを作成するならば問題ありませんが、インスタンスをシェアしている時はやはり他のスレッドにも影響します。つまり、シングルトンを使っているときは、インスタンス変数をいじることは、他のスレッドにも影響を与えるということです。

もちろん、例えば「設定値を変更したい」などというときには、他のスレッドにもいきなり変更があってよいかもしれません。(DBを使えよ、とは思いますが。)ただ、一時的な値の格納庫としてインスタンス変数を使うという手法では、マルチスレッドによって格納庫が別スレッドから汚されてしまうのです。

上のActionのような、「マルチスレッドで動作させると破綻する」ものを「スレッドセーフでない」と呼びます。ちなみに「スレッド危険」とは呼びません。そうでないもの(例えば、インスタンス変数に一時的な値を格納しない、あるいはそもそもスレッドごとに異なるインスタンスが作られることになっている)ものを「スレッドセーフ」と呼びます。ちなみに、通常のオブジェクト指向のプログラムでは、クラス変数はそもそも一時的な値の格納には使わないことでしょう。

じゃあ、どうするんだといえば、ローカル変数を使うことです。メソッドが呼び出されるたびにローカル変数は生成されます。値の受け渡しでは、多少長くなっても引数を利用すれば問題ありません。これは「ステートレスな実装」などと呼ばれることもあります。つまり、周りの状況(インスタンス変数の値など)に左右されずに実行することが可能ということです。

同期をとる

それでもなおかつシングルトンでインスタンス変数を使いたいということもあります。

その時には、synchronizedを使います。これには二つの使い方があり、メソッドにつける場合とブロックを作る場合があります。

   public synchronized ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response){
      this.form = form;
      ... // this.form を利用した処理。
      return ...;
   }

先ほどのActionも上記のように書けば(というか、この場合は本当は「インスタンス変数を使うな」が正解です)、スレッドセーフを実現することができます。

このsynchronizedがつけられたメソッドを実行している最中は、このインスタンスに対してロックがかけられます。ロックがかけられるというのは、このインスタンスに対する他のスレッドからのアクセスが待ち状態になるということです。(当たり前といえば当たり前ですが、「このメソッドが他スレッドから実行されない」ではなく「このインスタンスそのものが他から使えない」です。そうでないと、他のメソッドなりを経由して参照や書き換えが行えますから。)

従って、「インスタンス変数を使い始めてから使い終わるまで」それを独占できるわけですから、マルチスレッドによる、他からの勝手な書き換えは起きません。つまり、スレッドセーフを実現できます。

このsynchronizedは非常に強力ですが、「パフォーマンスが落ちる」という大きな問題があります。ほとんど使われないインスタンスならよいのですが、例えばActionがブロックされたら、他のリクエストは、あるリクエストが終わるまで待たされるわけです。マルチスレッドの利点が台無しになります。

スレッドごとの変数(ThreadLocal)

「synchronizedは使いたくないが、しかし、スレッドごとにインスタンス変数を持ちたいな」などという、のび太君のような虫のいいことを思う人もいるかもしれません。

どうしてもそれを実現したい場合はJavaにはThreadLocalという機構があります。
これは、(事実上)スレッドごとに別のインスタンス変数を持てるというものです。厳密に言えば、Javaの仕様通り、スレッド間でインスタンス変数を共用するのですが、そのインスタンス変数そのものはThreadLocalクラスのインスタンスを使い(これは共用)、実際の値は、そのインスタンスに対してget()/set()というメソッドを発行することでアクセスします。このget()やset()は実行中のスレッドごとに異なる値にアクセスしてくれます。

これは、便利なのだと思いますが、私は使ったことがありません。

その他

Actionがシェアされるからいけないなら、Actionがリクエストごとに作られればいいじゃない?」と、マリーアントワネットのようなことを思うかもしれません。
それはそれでありです。
そのためには、DIコンテナを使い…という話や、Struts 2.xを使い…という話もあるのですが、それはまたいつか。

*1:いちいち、1.xと書いているからにはStruts 2.xでは違うということです。