第3回は、前回に引き続きJava SE 7による変更点を見ていきます。今回は、パラレル処理や新しいバイトコードの導入に向けた取り組みです。Java SE 8の基盤となる機能として「Fork/Join Framework」と「InvokeDynamic」について解説します。

 Java SE 7のリリースがこれほど遅れてしまったのには、様々な理由がありますが、最も大きな理由は第4回で紹介するProject LambdaとProject Jigsawの仕様策定で意見がまとまらなかったことです。そこで、2010年のJavaOneでは、Project LambdaとProject JigsawをJava SE 8に先送りして、それ以外をJava SE 7としてリリースさせると発表しました。今回は、先送りされた機能を実装するために、Java SE 7に取り込まれた機能を解説します。

 皆さんが使っているCPUには、いくつコアが搭載されているでしょうか。二つまたは四つ、いやもっとたくさんという人もいることでしょう。最近では、携帯電話端末のCPUでさえも、2コアのものが増えてきています。このようにハードウエアは進化しているのですから、ソフトウエアも合わせて進化していく必要があるでしょう。

 とりわけ、マルチコアの時代のソフトウエアは、処理をなるべくパラレル化させて、遊んでいる状態のコアがなくなるようにしなくてはなりません。Javaは、1.0が登場したときからマルチスレッドを扱うことができ、J2SE 5.0ではCuncurrency Utilitiesというタスクをパラレル処理させるAPIが導入されて、さらに処理が行いやすくなっています。

 しかし、パラレル処理が十分ではありませんでした。Concurrency Utilitiesでパラレル処理されるタスクは、比較的粒度が大きめです。マルチコアをより有効に活用するには最粒度のタスクをパラレルに処理しなければなりません。そこで、導入されたのがFork/Join Frameworkです*1

 Fork/Join Frameworkでは、粒度の細かいタスクを扱えるようになります。しかし、問題なのはどうやってタスクを細分化するかということです。そこでFork/Join Frameworkでは、「分割統治法」を使ってタスクを細分化します。

 分割統治法は、再帰を使用して大きなタスクを分割していく手法です。例えば、ソートを考えてみましょう。図1のように八つの要素があるリストをソートするとします。はじめから全要素をソートするのは大変ですので、要素を半分に分割し、それぞれをソートすることを考えます。

図1●分割統治法を使ったソートのイメージ
図1●分割統治法を使ったソートのイメージ
[画像のクリックで拡大表示]

 半分の四つの要素をソートするのも大変なので、さらに半分に分割します。要素を最終的に1になるまで分割したら、そのまま結果を返します。一つ要素の結果が返ったら、その結果をマージしてソートします。さらに、その結果を使用して四つ要素をソートします、そして最後に八つの要素をソートします。

リスト1●分割統治法を利用したソート
リスト1●分割統治法を利用したソート
[画像のクリックで拡大表示]

 このアルゴリズムを使用したソートをマージソートと呼びます。Javaでもjava.util.Arraysクラスのsortメソッドなどがマージソートを採用しています。同じようにタスクを分割して処理を行い、それをまとめていくのが分割統治法です。これを擬似的なコードで書くとリスト1のようになります。再帰を用いて、分割したタスクを処理していきます。

 注目すべきなのが、再帰する部分が他に依存せず独立しているため、この処理をパラレルに処理することが可能だということです。Fork/Join Frameworkでは、分割統治法で分割した処理をパラレルに処理するために、java.util.concurrent.RecursiveActionクラスとjava.util.concurrent.RecursiveTaskクラスを提供しています。Recursiveは再帰を意味します。つまり、この二つのクラスは、再帰を用いたタスクを表しているのです。ActionとTaskの違いは戻り値があるかどうかです。

リスト2●RecursiveTaskでソートを書き直す
リスト2●RecursiveTaskでソートを書き直す
[画像のクリックで拡大表示]
リスト3●RecursiveTaskでソートを書き直す
リスト3●RecursiveTaskでソートを書き直す
[画像のクリックで拡大表示]

 リスト1をRecursiveTaskクラスで書き直してみたのが、リスト2です。Fork/Join Frameworkでタスクをパラレルに処理するには、(1)のforkメソッドを使用します。forkメソッドで起動したタスクが終了するのを待つのが(2)のjoinメソッドです。ただし、Recursive Taskクラスでタスクを記述しただけでは、パラレルに処理できません。そこで、これらのタスクを管理するために、java.util.concurrent.ForkJoinPoolクラスを使用します。リスト2のSortTaskクラスを起動する処理をリスト3に示します。

 ここまでパラレル処理の記述方法を見てきましたが、実は分割統治法を適用するような処理はそれほど多くありません。ソート以外にも数値計算などに使用できますが、一般的な用途では分割統治法を適用するようなタスクがなかなかないのが残念なところです。

 そこでJava SE 8では、イテレーション(繰り返し処理)をFork/Join Frameworkで書けるようになります。イテレーションであれば様々な場所で使えるので、Fork/Join Frameworkが使える場面も増えるはずです。また、第4回で紹介するProject LambdaのLambda式でタスクが記述できるようになるので、より簡単にパラレル処理を記述できます。Fork/Join Frameworkが本領を発揮するのは、Java SE 8からになりそうですね。