ミュータブルオブジェクト管理の歴史
マルチスレッド環境でのミュータブルオブジェクトの正しい管理は、非同期処理において最も難しい問題の1つです。先程紹介したJava Memory Modelという仕様は、この問題を解決するための一歩として、2004年Javaのバージョンが5.0になる際に導入されました。Java Memory Modelは非同期処理でのメモリ動作を、ロック機構などともに定めたものです(ちなみに多くの他の言語でもMemory Modelは言語に組み込まれGoのMemory ModelやC#のMemory Modelなどがあります)。Java 5.0はJava Memory Modelと同時にさまざまな非同期処理ツールをjava.util.concurrencyパッケージ下で提供しました。具体的には粒度の異なる各種のロック用のクラス、volatile変数やAtomicクラス、スレッドセーフなデータ構造などです。
Java 5.0はそれより前のバージョンに比べて非同期処理について大きく改善されたバージョンとなりましたが、そこで提供された非同期処理ツールは現代のものと比べるとまだまだ抽象度の低いものばかりでした。これらは大規模なコードベースになるとそのまま使うにはやや抽象度が低すぎて使いづらいものでした。よくある例として、以下の図で示すように、業務ロジック部分と非同期処理制御を安全に行うためのロジックとが入り組んでしまいソースコードの可読性・保守性が落ちるということがありました。

非同期処理ツールはその後も発展を続け、ミュータブルオブジェクトの管理にもさまざまなパターンやライブラリが提供されました。Akkaのアクターモデルもその中の1つです。次の項目ではAkkaの説明に入る前にイミュータブルオブジェクトについて説明します。
イミュータブル(不変)なオブジェクト
ここまで見てきたように、ミュータブルオブジェクトをマルチスレッド環境で管理する際は注意深さが要求されます。その原因は結局データ・レース、つまり「2つ(以上)のスレッドから、少なくとも一方が書き込みであるアクセス」です。そもそも書き込みアクセスが不可能であればデータ・レースは起きません。
そこで非同期処理のベストプラクティスとして、書き込みが不可能なイミュータブルオブジェクトを最大限利用することが広まりました。これにより、データ・レースに起因する難しい問題のほとんどを避けられます。
実はイミュータブルオブジェクトが推奨され始めた歴史は古く、先程のOracleのJava TutorialsのConcurrencyの章やJava Memory Modelでもすでに推奨されていました。しかし、Javaではイミュータブルなクラスを実装する際に全てのメンバ変数にfinalをつけることが要求されます。またイミュータブルオブジェクトは全てのフィールドにsetterを設けるJava Beansの作法に合わないこともあり、Javaでイミュータブルオブジェクトの利用を徹底するのは簡単ではなかったと言えます。
近年では関数型プログラミングの流行もあり、関数型プログラミングをサポートする言語では比較的簡単にイミュータブルなデータ構造を扱えることから、イミュータブルオブジェクトの利用はより広く浸透しています。

イミュータブルオブジェクトを使って非同期処理を表現するのに特に向いているものの1つに、近年流行しているストリーム処理があります。ReactiveXなどを知っている人もいるでしょう。ストリーム処理は、ひとつひとつ細切れな処理を数珠のようにつなぎ合わせ、処理全体を実現するものです。それぞれの処理はコールバック関数のように入力を受け取って出力を返す関数として表現します。

このようにイミュータブルオブジェクトの利用は非同期処理のベストプラクティスとして浸透しています。ただ、ありとあらゆる処理をイミュータブルオブジェクトのみで構成できるわけではありません。特に近年のアプリケーションはリアルタイム性とリッチな動作が強く求められる傾向にあり、クライアント側・サーバ側を問わずさまざまな「状態」をミュータブルオブジェクトとしてアプリケーションの各所に持つ場合があります。先程述べたキャッシュもその一例です。
