アクターモデルの利点
最大限イミュータブルオブジェクトを利用しつつも、現代のアプリケーションの要求に応えるため一部ではミュータブルオブジェクトを利用せざるを得ない。そんな時に有用なのがアクターモデルです。ここからは冒頭に紹介したアクターモデルをより詳細に説明し、なぜそれがミュータブルオブジェクトの管理を容易にし、かつ効率のよい非同期処理を実現できるのか見ていきましょう。

まずアクターはその内部状態を隠蔽します。つまりあるアクターのメソッドは他のアクターから直接呼び出せません。Akkaのアクターでは、直接アクターのメソッドを呼び出せるのはそのアクター自身のみです。メソッド呼び出しの代わりにアクター同士はイミュータブルなメッセージを送り合うことで協調して動作します。
アクターを使わない非同期処理プログラミングでは、マルチスレッド環境でミュータブルオブジェクトを利用する際、「メソッドをスレッドセーフに作る」という点に注意して実装します。これに対しアクターを使ったプログラムの開発者は、アクターのメソッドをスレッドセーフに保つ必要はありません。スレッドセーフ性はアクターの機構が保証してくれるのです。そのためアクターの内部を実装する時はスレッドセーフ性に気を使わなくても、ミュータブルオブジェクトにおける非同期処理の問題を引き起こさないようになっています。

ここで重要なのは、アクターが送受信するメッセージはイミュータブルであることです。メッセージがイミュータブルでないと、Akkaのアクターはスレッドセーフ性を保証できません。ScalaでAkkaを利用する場合はイミュータブルなメッセージを生成しやすいのですが、Javaから利用する場合でも必ずメッセージはイミュータブルにしてください。
//Javaのイミュータブルなメッセージの例
public static final class MyMessage {
public final String content;
public MyMessage (String content) {
this.content= content;
}
}
メッセージ送信側の動作を見てみましょう。メッセージを送信する際は送信側のアクターがtellメソッドを呼び出し、受信側のアクターのメッセージキューにメッセージを積みます。
//送信側アクター内のコードで、以下のように受信側(receiverActorRef)のtellメソッドを呼ぶ receiverActorRef.tell(new MyMessage(“message content”))
tellメソッドはアクター自体のメソッドではなくActorRefというアクターへの間接的な参照を表すオブジェクトのメソッドです。また受信側アクターのメッセージキューはアクターの外部にあるもので内部状態ではありません。先程述べたように送信側のアクターは受信側のアクターに直接アクセスしているわけではないのです。

Akkaでは基本的にひとつひとつのアクターに個別のメッセージキューが付与されます。メッセージキューのスレッドセーフ性により、複数の送信側アクターが単一の受信側アクターに対して同時にかつ安全にメッセージを送れます。

ここまではメッセージ送信側がどうしてスレッドセーフに動作するのかを説明してきました。今度はなぜメッセージ受信側がスレッドセーフに動作するのか見ていきましょう。
受信側のアクターではメッセージキューに積まれたメッセージを最初に受信したものから順番に処理していきます。また、アクターの内部状態はメッセージ処理によってのみ変化します。その他の方法でのアクターの内部状態の変更は不可能です。メッセージの処理は受信側アクターでのメッセージ処理メソッドによって行われます。

加えて、受信側アクターでのメッセージ処理中に別のアクターに対して新たなメッセージを送信できます。これによりアクターからアクターへと次々にメッセージを伝播させて複数アクターにまたがる処理を実現できます。

アクターのメッセージ処理メソッドは、別のアクターから直接呼び出されることはありません。受信側のアクターがメッセージキューからメッセージを取り出した時点で自身のメッセージ処理メソッドを呼び出します。メッセージ処理メソッドを呼ぶのはアクター自身のみであり、メッセージ処理メソッドは競合して呼び出されません。そしてメッセージの受信側では、1つのメッセージを処理し終えるまでは次のメッセージの処理は始まりません。常に現在のメッセージ処理メソッドの呼び出しが完了してから、次のメッセージを処理するためにメッセージ処理メソッドの新たな呼び出しが始まります。つまりアクターのメッセージ処理メソッドはアトミックな処理です。

もし受信側アクターが常に単一のスレッドで実行されるとすれば、これだけの機構でスレッドセーフ性を保証できます。しかし実際は2つの異なる時点では、同じアクターのメッセージ処理メソッドが違うスレッドによって実行される可能性があり、先程述べたメモリ矛盾問題を防ぐための機構が必要です。

Akkaはこのメモリ矛盾を防ぐ機構をvolatile変数によって実現しています。volatile変数は、1つのスレッドで書き込みが終わると即座に他の全てのスレッドから書き込みが観測可能となります。

ここまでAkkaのアクターがいかにスレッドセーフな仕組みを実現しているか説明してきました。重要なのはAkkaアクターを使ったプログラムの開発者は、アクター内部で競合の心配がないのでスレッドセーフなデータ構造を使う必要もなければ、ロックやsynchronized、volatileを使う必要もありません。Akkaの機構側で抽象度の低い非同期ツールを使用する部分をすでに実装しているので、アクターを使ったプログラムの開発者はそれらを気にしなくてすみます。
まとめ
今回はミュータブルオブジェクト管理の難しさ、Akkaのアクターモデルの仕組みを説明しました。Akkaがスレッドセーフなメッセージキューやvolatile変数などを組み合わせでスレッドセーフ性を実現し、アクター内部の実装ではスレッドセーフ性に気を使わなくてよい点がおわかりいただけたと思います。
次回の記事ではAkkaアクターに関わるさらに詳しい仕組みと実装を解説し、アクターの使い方をより豊富なコード例とともに紹介します。
