javaでのスレッド制御(joinとsynchronized)

javaでは、スレッドを立てて処理を並列に行うことができます。
しかし、並列に処理を行う際、処理順を制御しなければならないことがあります。

処理順の制御方法として基本的な方法として、joinを使う方法とsynchronizedを使う方法があります。
joinを使うことで、スレッドが終わるのを待つことができます。
また、synchronizedを使うことで、複数のスレッドが同じ処理を同時に行わないように制御することができます。

以下は、joinやsynchronizedを使って制御を行うサンプルコードです。
1つのスレッドで5回の処理を行うサンプルであり、そのスレッドを2つ立てます。
また、スレッド間で同一のオブジェクト(staticなオブジェクト)を共有しており、そのオブジェクトでは合計何回処理が行われたのかをカウントしています。

制御を行わない場合は、どちらのスレッドが先に処理するのかが定まりません。また、共有オブジェクトでのカウントを2つのスレッドで同時に行うためにカウントも上手く行きません。
joinによる制御を行う場合は、スレッド1の5回の処理が終わってからスレッド2の処理に移る、という制御が可能になります。
synchronizedによる制御を行う場合は、スレッド1とスレッド2が同時に動いても、共有オブジェクトでのカウントは同時には行われず、上手くカウントすることができます。

【サンプルコード】

・ThreadMain.java

・ThreadBody.java

・ThreadCounter.java

【実行結果】

・そのまま実行した場合

・①のコメントアウトを外した場合(join)

・②のコメントアウトを外した場合(synchronized)


いかがでしたでしょうか。

性能要件を満たすためにスレッドが必要になることがあるのですが、その際にスレッド制御の方法を知らないと処理の同期が取れずに障害になってしまうことが多いです。
今スレッドを使っていなかったとしても、いつ使うようになるかわかりませんので、こういったスレッド制御の方法を知っておいて損はありません。

では、また次回!

単なるstaticとsingletoneパターンの違い

オブジェクト指向プログラミングのプログラミング手法で、「singleton(シングルトン)」と呼ばれる手法があります。
この手法は、プロジェクト内で共通的に使われるインスタンスを1つだけ予め作成し、外部のクラスにはそのインスタンスを使用させる、という手法です。

この手法を学んだ時に「インスタンスを1つにすれば良いなら、クラス変数やメソッドをstaticにするだけで良いのでは?」と思ったので、試しにテストコードを書いてみました。
単純なstaticでは使用者側で好きにインスタンスを作成できる(実態としては1つ)のですが、singletonとしてインスタンスを作成すれば使用者側でのインスタンス作成を防ぎ、明示的にインスタンスを1つにできることを確認しました。

【テストコード】

・StaticMemory.java

・SingletoneMemory.java

・MemoryTestMain.java

【実行結果】


いかがでしたでしょうか。

「singleton」は、GoFの23個のデザインパターンの内の1つに数えられる手法です。
デザインパターンはオブジェクト指向プログラミングのプログラミング手法をまとめたものであり、効率的なプログラミングを行う上で有効ですので、これからも紹介していくと思います。

では、また次回!

java:イミュータブルなクラスを作る方法

「イミュータブル」とは「不変」という意味で、オブジェクト指向の世界では「状態(クラス変数)がオブジェクト生成時から変更されないこと」を指します。
有名所では、Stringがイミュータブルなクラスとして知られています。

イミュータブルなクラスを自作するためには、以下の条件を満たすようにクラスを作成する必要があります。

①クラス変数はprivateで定義する

クラス変数が外のクラスから直接書き換えられるのを防ぐため

②setterメソッドを定義しない

クラス変数を書き換えるメソッドは定義しない

③クラスをfinalで定義する

サブクラスでクラス変数を書き換えられるのを防ぐため

④ミュータブルなオブジェクトへの参照を含んでいない

ミュータブルなオブジェクトを変更する処理を記述しないようにするため

クラスをイミュータブルにするメリットとしては、内部の状態の変化を気にする必要が無くなるために処理を追いやすくなるというのがあります。
関数型プログラミングにも通じる考え方なのですが、オブジェクト生成より後で内部の状態が書き変わることがないため、クラスのメソッドが返す値が一定となり、内部の状態の変化を気にしながらトレースする必要がなくなります。
また、
hoge2 = hoge1;
のようなオブジェクトのアドレスのコピーが使いやすくなります。
イミュータブルではないクラスでこのコピー方法を用いると、hoge2の状態の変化がhoge1にも影響してしまい(hoge1とhoge2で同じアドレスを指しているため)、思わぬ影響が出ることがあります。
しかし、イミュータブルなクラスであれば、状態を変化させようと思えば新たにオブジェクトを作り直して新たなメモリ領域を確保しなければならないので、hoge2の状態を変化させても(オブジェクトを作り直しても)hoge1へ影響するのを防ぐことができます。
イミュータブルではないクラスでも、オブジェクトの中身全体をコピーすれば思わぬ影響を防ぐことはできるのですが、オブジェクトのアドレスをコピーする場合と比べてメモリ使用量が増え処理も遅くなります。

以下で、イミュータブルなクラスを定義してそれを使用するサンプルコードを示します。

【サンプルコード】

・ImmutableCalc.java

・ImmutableTestMain.java

【実行結果】


いかがでしたでしょうか。

オブジェクトの内部の状態を意識する手法には多くの技術者が慣れ親しんでいると思いますし、オブジェクトのアドレスのコピーも便利ですが、それが思わぬ影響を及ぼすこともあります。
思わぬ影響を防ぐために、イミュータブルなクラスを作成する手法は覚えておいて損はないでしょう。

これからも役に立つ情報を発信していきたいと思います!

java8:関数型インターフェースの背景にある考え方

【前置き】

Java8から関数型インターフェースが使用可能になりました。
具体的に「ラムダ式」「Stream」「Optional」「Files」と言った方がわかりやすいでしょうか。

関数型インターフェースの使用を半ば強制されるフレームワークが登場していたり(例:Apache Spark)、関数型インターフェースでJavaを書く開発者も増えてきたので、目にすることも多くなってきたかと思います。

関数型インターフェースは関数型プログラミングをサポートするものであるため、従来からJavaでサポートされていたオブジェクト指向プログラミングとは発想が異なります。
そのため、従来のJavaを学習してきた方にとっては抵抗感を感じるものであると思います。

今回の記事では、抵抗感を少しでも減らすために、関数型プログラミングの考え方を簡単に紹介したいと思います。

【サンプルコード】

言葉で説明するよりも先にサンプルコードを見た方がわかりやすいと思うので、サンプルコードを先に紹介します。
年齢のリストから30代の人数を数える、というプログラムです。
ごく短いプログラムですので、お付き合いください。

・FunctionTest.java

・実行結果

【関数型プログラミングの考え方】

関数型プログラミングでは、以下のことを実現しようとしています。
色々難しい用語(例えば「副作用」等)はあるのですが、今回は用語を使わずに簡潔にまとめます。

・内部状態(State)を排除する

最も本質的な考え方です。

関数型プログラミングでは、内部状態を排除することを目的としています。
「内部状態」とは、上記のコードで言うと「count」や「i」を指します。

内部状態が入りこんでしまうと、内部状態により関数の結果が変わってしまうため、内部状態を把握する必要が出てきてしまい、可読性が悪化します。
(把握のために「count」や「i」をトレースする必要が出てきてしまう)
把握しきれずに意図しないバグを出してしまうことも珍しくありません。
内部状態を排除して、品質を上げよう、という発想です。

また、コンピュータにとっては内部状態は重要ですが、人間にとってはやりたいことを実現できれば良く、内部状態は重要ではありません。
重要ではない記述を削減することでコードを完結にしたい、という発想もあります。

Java8のラムダ式では、ラムダ式の外部で定義された変数の値をラムダ式の内部で変更することを禁止されています(コンパイルエラーになる)。
その背景には、内部状態の排除があると思っています。

・自然言語に近い形で処理を記述する

これは、コードが簡潔になった結果生じた副次的な考え方かもしれません。

関数型プログラミングでは、関数を組み合わせることにより処理を実現します。
関数を次々とつなぎ合わせるように記述することで、ソースコードが自然言語に近い形になります。
わかりやすく言えば、ソースコード自体がコメントのようになります。

例えば、サンプルコードでは「年齢のリストから30代の人数を数える」という処理を行おうとしています。
従来のプログラミングでは、これを実現するためにforループとかカウント用の変数を使用しており、何をしているのか把握するためには、内部状態をトレースして意図を汲み取る必要があります。
しかし、関数型プログラミングでは、
「list.stream().filter(x -> x >= 30 && x <= 39).count()」→
「listを30<=x<=39でfilterしてcountする」
と読めるため、
「年齢のリストから30代の人数を数える」
という処理であることを自然に把握することができます。


いかがでしたでしょうか。

IT業界、特にSIer業界だと、情報処理技術者試験を軸にして知識を身に付けることが多いかと思います。
しかし、情報処理技術者試験では手続き型プログラミングやオブジェクト指向プログラミングを中心とした出題で、関数型プログラミングが扱われることは全く言って良いほどありません。
そのためとっつきにくさは拭えないと思いますが、先進的な企業を中心に関数型プログラミングを取り入れる企業も出てきています。
これからのことを考えると、せめて、関数型プログラミングに対する抵抗感は払拭するべきではないかと思っています。

今回はこれで締めくくりたいと思います。
では、また来週!

java:ミュータブルな参照型変数の初期化の注意点

ミュータブルな参照型変数を初期化する場合、初期化の方法を間違えると他の変数も一緒に初期化してしまいます。
この記事では、ミュータブルな参照型変数の初期化方法を説明します。

【基本データ型変数と参照型変数】

変数は大きく分けて、基本データ型変数(プリミティブ型変数、値型変数)と参照型変数(オブジェクト型変数、クラス型変数)の2つに分けることができます。

基本データ型変数とは、以下の8つの内の何れかの型で定義された変数であり、オブジェクトを持ちません。
・byte
・short
・int
・long
・float
・double
・boolean
・char

参照型変数は以上の8つの型以外の型で定義された変数であり、オブジェクトを持ちます。
実際には、メモリ領域の番地を指し示すポインタのようなものが格納され、ポインタを辿って値を参照します。
C言語のポインタの概念を理解しているとこの概念も理解できます。C言語のポインタについては以下のページをご参照ください。

C言語:ポインタの概念の図解

【イミュータブルとミュータブル】

参照型変数は、更にイミュータブルとミュータブルの2つに大別することができます。

イミュータブルは、日本語で言うと「不変」という意味であり、オブジェクトの中の値を変更することができない変数を指します。
setter等でオブジェクトの中の値を変更できないようにする、メンバ変数をprivateで定義してオブジェクトの中の値を変更できないようにする、等の条件を満たすと、イミュータブルな型となります。
イミュータブルな型となる条件はかなり例外的であり、自分でクラスを作成する場合は意識的に条件を満たそうとしなければイミュータブルな型にはならないはずです。
標準で用意されている型の中では、String型がイミュータブルな型として有名です。
イミュータブルな参照型変数はオブジェクトの中の値を変更できないため、中の値を変更する場合は新たにメモリ領域を確保してオブジェクトを作り直すような動きになります。今回取り上げる記事上は、基本データ型変数と同じような動きになります。

ミュータブルはその逆で、オブジェクトの中の値を変更することができる変数のことであり、多くの参照型変数はこちらに該当します。
String型も、配列にした場合はミュータブルな型になります(配列がミュータブルなので)。
そして、注意が必要なのはこのミュータブルな参照型変数です。

【ミュータブルな参照型変数の初期化の注意】

参照型変数の中には、ポインタが格納されています。

例えば、

とした場合、fugaには「{“あ”,”い”,”う”}」が入るわけではなく、fugaのアドレスが格納されます。
つまり、hogeとfugaは同じ位置を指し示すことになります。

ここで、fugaのみ初期化(値を変更)しようとしたとします。
String型のようにイミュータブルな型であれば、fugaの値を変更された時点でメモリ領域が新たに確保されるので、hogeとfugaは別々の値となり、何の問題もありません。
しかし、String[]型のようにミュータブルな型の場合は、hogeとfugaが同じ位置を示しており、新たにメモリ領域が確保されることもないので、hogeが示す値も一緒に書き変わってしまいます。

例えば、

のようにfugaを全角スペースで初期化しようとすると、fugaだけでなくhogeも初期化されてしまいます。

これを防ぐためには、以下の2つの手順を踏む必要があります。
1.初期化したい変数にnullを代入してメモリ領域への参照を切る
2.newで初期化したい変数用のメモリ領域を新たに確保する

以下、サンプルコードで動きをまとめたいと思います。

【サンプルコード】

【実行結果】


「参照型」や「イミュータブル」といった概念は、プログラム経験の浅い人には難しいと思います
私の場合はC言語でポインタの概念を理解してからこれらの概念を学んだのですが、それでも初めの内はjava独特の仕様に戸惑いました。
しかし、概念を理解すればコーディングする上で便利だと感じることもあるので、この記事を通して理解を深めていただければ幸いです。

今後も、つまずきやすいポイントを記事にしていきたいと思います!