透2017/5/92019/5/14スレッドが2つ以上複数あり同時進行で処理されることを「マルチスレッド」といいます。「マルチスレッド」で複数同時並行で処理する場合に、別のスレッドから意図せずに値が変更できるとしたら、これってとても恐いことですよね。アマゾンのようなショッピングサイトで自分の買い物データガ他人の買い物データで変更されるなんてことが起きてしまったらショッピングなんてできなくなります。そんなことにならないために使われるのがThreadLocalクラスです。この記事では、そんなThreadLocalクラスについて以下の内容で解説していきます。まとめThreadLocalクラスとはThreadLocalクラスについて解説する前に、Javaの変数とそのメモリ管理について解説しておきましょう。Javaでは「クラス変数」は共有メモリ領域に保存されます。この領域を「ヒープ領域」といいます。ちなみに、「クラス変数」とはクラス内の静的な(static)メンバ変数のことです。この領域は複数のスレッドで共有することができて、他のスレッドによって書き換えることができます。これに対してプリミティブ型のローカル変数はスレッドごとの固有のスタック領域に格納されます。この領域はスレッド固有の領域なので、他のスレッドによって書き換えられることはありません。これを「スレッドセーフ」といいます。ThreadLocalクラスを使うことで「クラス変数」についてもスレッド固有で管理することができます。「スレッドセーフ」を実現することができるのです。オブジェクトを示すローカル変数(参照型のローカル変数)の場合、プリミティブ型同様スタック領域に格納されますが、ヒープ領域にオブジェクトの実体が存在するためその限りではありません。スレッド毎に値を保持する方法マルチスレッドでスレッド毎に値を保持する場合に、ただのTreadクラスを使用した場合とThreadLocalクラスを使用した場合で、それぞれクラス変数の値がどのようになるかみていきましょう。Threadクラスでクラス変数を扱う場合の注意点まずはThreadLocalクラスを使用せずにただのThreadクラスを使用した場合に、クラス変数の値がどのようになるのかみていきましょう。1このサンプルコードでは、Intクラスでクラス変数「i」を宣言しています。Intクラスにはクラス変数「i」のゲットメソッドであるgetIntメソッドとセットメソッドであるsetIntメソッドがあります。ThreadTestクラスではコンストラクタで取得した変数「val」の値をIntクラスのクラス変数「i」に代入しています。「val」と「i」の値が同じ値となっているか表示し、約1秒おきに5回繰り返すメソッドをrunメソッド内で定義しています。これをスレッドで動かせるようにしています。これらをMainクラスのmainメソッド内でインスタンス化しています。「val」の値が0(ゼロ)のThread-0と「val」の値が1のThread-1を作り、Thread-0がスタートしてから約3秒後にThread-1がスタートするようになっています。この場合Thread-0とThread-1の両方からクラス変数「i」にアクセスできます。Thread-1がスタートしてThread-0が終了するまでの約2秒間は2つのスレッドが並列で処理を行っているので、クラス変数「i」の値が意図しない値となっています。このような問題を解決するためにThreadLocalクラスを使用します。ThreadLocalでクラス変数を扱う方法それではサンプルコードを使ってThreadLocalクラスの使い方をみていきましょう。1このサンプルコードでは、先ほどの例に加えてNewIntクラスを作成しています。Intクラスのメンバは「非static」に変更しています。NewIntクラス内でThreadLocalクラスを使用します。ThreadLocalクラスの使い方として、まずThreadLocalクラスをインスタンス化し、initialValueメソッドを使ってスレッドの初期値を取得します。その次にこの例ではgetNewIntメソッドを定義して、スレッドの値を取得できるようにしています。NewIntクラスのgetIntメソッドでIntクラスのgetIntメソッドを、setIntメソッドでIntクラスのsetIntメソッドを代替しています。TreadTestクラスのrunメソッド内のIntクラスのクラスメソッドをNewIntクラスのクラスメソッドで代替することで、スレッド固有のクラス変数として意図通りに扱えています。ThreadLocalRandomで乱数を扱う方法ThreadLocalクラスを使ってスレッド固有の変数を使用しながら、乱数を使用する場合は注意が必要となります。乱数を使用する場合、通常Math.Randomメソッドなどを使用しますが、この場合も同様にMath.Randomメソッドを使用すると処理速度が極端に遅くなる可能性があるからです。この場合はThreadLocalRandomクラスのThreadLocalRandom.current().nextIntメソッドを使用します。実際に使用する例をみてみましょう。}実行結果:ThreadLocalRandomクラスでの処理時間は557ミリ秒ThreadTestクラスではこれまでの例と同様にNewInt.setIntメソッドとそれに加えて乱数を発生させるThreadLocalRandom.current().nextIntメソッドの2つのメソッドを定義しています。これらのメソッドはスレッドで実行するrunメソッド内で定義されています。Mainクラスのmainメソッドでは1万個のスレッドを作成し、並列処理を行っています。その処理時間がこの場合は557ミリ秒でした。ちなみに、ThreadLocalRandom.current().nextIntメソッドの代わりにMath.Randomメソッドを用いると、55730ミリ秒処理時間がかかり約100倍の差が確認されました。このように処理時間に大きなロスが発生するので、乱数が必要な場合はThreadLocalRandom.current().nextIntメソッドを使用しましょう!removeを使ったメモリリークへの対応ThreadLocalクラスを使用する場合の注意点があります。それはメモリリークが発生する可能性があることです。メモリリークを発生させないためには処理が終わったスレッドはremoveメソッドを使って削除するようにしましょう。removeメソッドの使い方は下記のサンプルコードのとおりです。ThreadLocalRandomクラスでの処理時間は574ミリ秒このサンプルコードでは、ThreadLocalクラスを使用しているNewIntクラス内でThreadLocalクラスのインスタンス「tl」のremoveメソッドを実行するremoveメソッドを作成しています。クラスのrunメソッドにおいてfinallyブロックの中で実行されるようにしています。このようにすると処理が終了する度にスレッドは削除され、メモリリークの発生を防止することができます。コンストラクタについて詳しく知りたい方へこの記事では初期値を設定するためにコンストラクタを使用しています。2019年4月8日Threadの使い方(sleepで停止、joinで同期)Threadクラスを使用してsleepメソッドで一時的に処理を停止したり、joinメソッドを使って別のスレッドの処理が完了するまで待機して同期させるといったことができます。2019年4月29日まとめここでは、ThreadLocalについて使い方や使う上での注意点について説明しました。スレッド固有の値を取り扱う場合は他のスレッドから値にアクセスできないように、また乱数を一緒に使う場合やメモリリークなど注意する点がいくつかあります。マルチスレッドの記述は最初は複雑だと感じるかもしれませんが、慣れて使いこなすことができるようにこの記事を何度も参考にして下さいね!TweetPocket無料でSEからWebエンジニアへ転職しませんか?侍エンジニア塾では、完全未経験の方から現在SEだけどプログラミングはやっていないという経験者まで、幅広い方々の人生を好転させるプログラミング指導を行ってきました。SEの方とお話していくなかで、システムエンジニアという職業だけどコードが書けない事務作業が多くスキルがないため将来が不安スクールに通うと完全未経験者と同じスタートになるからレベルが合わないという、すでに知識があるSEならではのお悩みがあることに気づきました。弊社では、マンツーマンでレッスンを行いますので、現在お持ちの知識レベルからカリキュラムを作成いたします。こちらの転職コースは無料で受講を始められて転職成功でそのまま卒業できるというとてもお得なコースとなっています。知識のあるSEといっても転職は年齢が若いほど受かりやすいため、まずは無料体験レッスンで今の現状や理想の働き方について一緒に考えていきましょう。熊本在住のフリープログラマ兼ライターです。C/C++/C#、Java、Python、HTML/CSS、PHPを使ってプログラミングをしています。専門は画像処理で最近は機械学習、ディープラーニングにはまっています。幅広くやってきた経験を活かしてポイントをわかりやすくお伝えしようと思います。お問合せはこちらでも受け付けています。