マルチスレッドプログラミング - Runnable interface の実装
前の記事では、Thread class を継承する方法を試しました。
マルチスレッドプログラミング - Thread class の継承 - 社内se × プログラマ × ビッグデータ
今回は、Runnable interface を実装することで、マルチスレッドを実現していきます。
こんな感じで作成できます。
public class Dog extends Animal implements Runnable { }
この実装のメリットとしては、継承するクラスの対象として、Thread クラス 以外を指定することができます。
Runnable インタフェースは、run() メソッドを実装しているので、オーバーライドします。
public class Dog extends Animal implements Runnable { @Override public void run() { // anything task } }
このスレッドを開始するためには、ワーカーとなるクラスのインスタンスを生成して、Threadインスタンスに渡してあげます。
そして、start() メソッドにより開始します。
Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start();
Thread クラス自体が Runnable インタフェースを実装しています。
それと同じように Runnable インタフェースを実装したクラスを定義してあげていることになります。
Thread クラスに渡してあげることで、start() が使えるようになります。
public class Thread extends Object implements Runnable
まとめ
一つのスレッドを開始するために、最低2つのインスタンスを生成する必要がありますが、Thread 以外のクラスを継承できるのはいいですね。
マルチスレッドプログラミング - Thread class の継承
Apache spark などマルチスレッドのプログラミングを行う上で非常に便利なライブラリが登場していますが、それがあまりにも便利すぎるが故に、それ以前のマルチスレッドプログラミングについても学んでおきたいところです。
そもそも Apache spark のライブラリ自体が、内部的には元来のマルチスレッド手法を踏襲していたりもするところもあると思いますので、やはり知っておいた方が、より理解も深まるだろうと思います。
Java でマルチスレッドプログラミングを行う方法はいくつかあると思いますが、最も基本的な?ものは、Thread クラスを継承して使用するやり方かと思います。
Thread クラスを継承したクラスのインスタンスを生成し、start メソッドを実行することで、新たなスレッドを開始することができます。
以下はどこにでもありそうな簡単なサンプルです。
インスタンス生成時に渡された intervalMs の値 sleep するだけのスレッドです。
public class MyThread extends Thread { private long intervalMs; private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); public MyThread(long intervalMs) { this.intervalMs = intervalMs; } @Override public void run() { System.out.printf(dateFormat.format(new Date()) + " [%s] my-thread startging.\n", Thread.currentThread().toString()); try { Thread.sleep(intervalMs); } catch (InterruptedException ie) { // nothing to do } System.out.printf(dateFormat.format(new Date()) + " [%s] my-thread ending.\n", Thread.currentThread().toString()); } }
これを main メソッドから呼び出します。
5秒 sleep するスレッドと、10秒 sleep するスレッドを生成しています。
public static void main(String args[]) { SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); System.out.printf(dateFormat.format(new Date()) + " main thread at startging.\n", Thread.currentThread().toString()); MyThread myThread1 = new MyThread(5000); myThread1.start(); MyThread myThread2 = new MyThread(10000); myThread2.start(); System.out.printf(dateFormat.format(new Date()) + " main thread at ending, actually waiting my-thread ending.\n", Thread.currentThread().toString()); }
結果は以下のようになりました。
01:06:36 main thread at startging. 01:06:36 [Thread[Thread-0,5,main]] my-thread startging. 01:06:36 main thread at ending, actually waiting my-thread ending. 01:06:36 [Thread[Thread-1,5,main]] my-thread startging. 01:06:41 [Thread[Thread-0,5,main]] my-thread ending. 01:06:46 [Thread[Thread-1,5,main]] my-thread ending.
main スレッドは直ちに最後の行に到達していますが、スレッドは直ちに終了はせずに、すべての my-thread が終了するまで待ってくれています。
まとめ
1. Thread クラスを継承した独自のスレッド用のクラスを生成する。
2. run() 内に実際に実行させたい処理を書く。
3. start() でスレッド開始する。
ダイレクトバッファについて調べる機会
きっかけは、Apache spark でアプリケーション動かしていた時に、java.lang.OutOfMemoryError: Direct buffer memory に出くわしたこと。
ダイレクトバッファのこともよく理解せずに、spark プログラミングするのは申し訳ない気持ちになったので。。
ダイレクトバッファとは
- ダイレクトバッファは、NIOで新しく導入された機能。
- 「ヒープ」外のメモリ領域に作成される。
- 「ヒープ」上に作成される通常のバッファ(非ダイレクトバッファ)に比べて高速。
- ダイレクトバッファはByteBufferについてのみ作成可能。
- 高速に入出力できるが、作成や削除の処理は非ダイレクトバッファより時間がかかる。
- 容量の大きいバッファが必要なとき、作成したバッファを繰り返し使用するときに使用するべき。
バッファとは
- プリミティブ型のデータを保存するための入れ物。
- バッファクラス(ByteBuffer, IntBuffer など)は、boolean型以外のすべてのプリミティブ型に対して用意されている。
- バッファクラスは、java.nioパッケージに属していて、java.nio.Bufferクラスを継承している。
NIO(New I/O)とは
- JDK1.4で追加された機能。java.ioパッケージの入出力機能を補足する。
java.ioパッケージとは
- データ入出力用のクラスがまとめられているパッケージ。
ダイレクトバッファをつくってみる
ダイレクトバッファの作成
ByteBuffer bufferA = ByteBuffer.allocateDirect(5);
非ダイレクトバッファの作成
ByteBuffer bufferB = ByteBuffer.allocate(5);
Java/TryDirectBuffer.java at master · blueskyarea/Java · GitHub
ダイレクトバッファは作成遅いが、アクセス速い
-- allocate direct buffer -- 1002647 nano seconds -- allocate non-direct buffer -- 80622 nano seconds -- access to direct buffer 100000 times -- 16322116 nano seconds -- access to non-direct buffer 100000 times -- 29337490 nano seconds
上記の結果から、以下の点について軽く確認できました。
- ダイレクトバッファは、非ダイレクトバッファよりも作成に時間がかかる。
- ダイレクトバッファは、非ダイレクトバッファよりも繰り返されるアクセスに対して速い。
そういえば、ダイレクトバッファは「ヒープ外」に存在するのだから、GCの対象外になるはず。
なので、十分に大きな値を上限値として割り当てることが推奨されるのかもしれない。
Java challenge どこに違和感を感じればよかったのかが分からない
以下のコード、コンパイル通ると思いますか?っていうもんだい。
public class PolymorphismChallenge { public static void main(String[] args) { System.out.println(new Chris().getCharacter().getSimpleName()); } abstract static class STARSSoldier { public STARSSoldier getCharacter() { return new Chris(); } } static class Chris extends STARSSoldier { @Override public Chris getCharacter() { return new Chris(); } } }
Java/PolymorphismChallenge.java at master · blueskyarea/Java · GitHub
1.Chris インスタンスが生成される。
2.getCharacter() が呼ばれ、Chris インスタンスが返却される。
これだけなので、とくにコンパイルは通るように思えます。
話をややこしくしようとしているのは、Chris が STARSSoldier を継承しているところだと思います。
ただ、それも単純に考えれば、親クラスのメソッドをオーバーライドしているだけです。
実際、このコードを実行してみると、コンパイルは通って以下の出力が得られました。
Chris
ひっかけ問題があった時、するどい方は何でひっかけようとしているのかまで分かると思いますが、この問題に関してはそのようなところに想像が至らなかった。
まだまだ修行がたりない。
VirtualBox ゲストOS のネットワークが頻繁に Up/Down していたが NATネットワークに変えたら解消
この記事の続きになります。
http://blog.hatena.ne.jp/blueskyarea/blueskyarea.hatenablog.com/entries
ネットワークが毎分切断するような状況だと、ダウンロードするにも途中で切れるから毎度再開しないといけないし、再開モードが機能しない場合は、そもそもダウンロードを完了させることが非常に困難な状況になってしまったので、続けて調べていたところ公式ページに回答がにありました。
#13839 (ethernet frequently disconnecting / reconnecting after upgrade to 4.3.22 => Fixed in SVN) – Oracle VM VirtualBox
同じような状況下になった人は、ネットワークアダプタの設定を NATネットワークに変更することで解消したとのこと。
私は NAT で設定していたので、わらにもすがる気持ちでためしてみたところ、「切れない! ログにも LINKUP、DONW 出ていない! これで数分間のダウンロードにたえられる!」ということになりました。
まぁ、なんとか普通に使えるようになっただけではあるのですが。
これは VirtualBox 4.3.22 の問題だったようですね。
このバージョンで以前は問題なく使用していたので、VirtualBox の問題とは思っていませんでした。
可能性をもっと広げれば、もう少し早く解決できていたなと思いつつ、普通に使えるようになったことをよろこんでいます。
Mockito のチュートリアル記事を読んだ感想
これです。
www.discoversdk.com
実際のところ、Mockito って Maven のリポジトリを見ても、12/31/2014以降は新しいバージョンが出ていないくらい完成された?フレームワークなのかもしれない。
ただ、この記事は 5/25/2017 に更新されたのもので、個人的にそんなに詳しくないので、読んでみました。
Mockito とは?
Javaのオープンソースのフレームワークで、モックを簡単に作れるよ。
モックというのは、ユニットテストで使えるダミーの機能(実装はされていない)みたいなもの。
※この記事には、最新バージョンが Mockito release is version 1.9.5 と紹介されているけど、1.10.19 までは出ているはず
Mockito と JUnit
簡単に試せれば何でも良いので、以下の2つのクラスを定義しました。
1) 1 - 100 内の数字をランダムに生成するクラス。NumberGenerator.class
2) 2 つの数字の平均を求めるクラス。AverageCalculator.class
正直、プログラミングの練習のために、適当なクラスを考えるのは苦手です。
Mockito を使うためには
1. テストクラスに @RunWith のアノテーションをつける。
2. テストフィールドに @Mock もしくは @Spy のアノテーションをつける。
要するに、モックをつくりたいオブジェクトはここで定義する。
3. モックを注入するクラスに、@InjectMocks のアノテーションをつける。
要するに、テスト対象のクラスということになると思います。
アノテーションをつけることはわかるのですが、それによって内部的にどういう動きをするようになるのかが知りたいところです。
プログラミングしていてよく感じることですが、なぜこの一行を加えるだけで、全く違う動きになるのか?というところは、ソースコードが公開されているライブラリであれば、調べようと思えば調べられることなんですけどね。
知らなくても何とかなる部分でも、興味があればトコトン調べれば良いだろうし、そこまで無いのであれば、もっと興味のある方に時間をかけた方が良い部分だとは思います。
テストコード
以下のようになりました。
@RunWith(MockitoJUnitRunner.class) public class AverageCalculatorTest { @Mock NumberGenerator numberGeneratorMock; @InjectMocks AverageCalculator averageCalculatorMock; @Test public void test() { // given given(numberGeneratorMock.generateNumber()).willReturn(50); // when Integer average = averageCalculatorMock.getAverage(); // then then(average).isEqualTo(50); } }
普通に JUnit を実行させると、テスト結果が返ってきます。
おおまかな流れは以下のようになっていると思います。
1. NumberGenerator のモックが作られる
2. AverageCalculator のモックに NumberGenerator のモックが注入される
3. generateNumber() の戻り値が 50 で固定される
4. よって getAverage() の結果は 50 となる
用語
Dummy: ビジネスロジックに全く関わりのないコードのためのオブジェクト。
例えば、パラメーターを関数に渡すだけとか。
> ダミーと呼ぶのは、聞いたことがないですね。
Fake: 本番環境では使用しないオブジェクト。
例えば、メモリ上で動作する H2 データベースとか。
> これも意識して、フェイクと呼んだりしていない。
Stub: テスト中において、実行結果を予め定義してあるオブジェクト。
> スタブはよく耳にすると思いますが、モックとの違いがよく議論されていますね。
Mock: テスト中において、実行結果を予め定義してあるオブジェクトで、各実行の期待値が記録(保持)されている。
> スタブとの違いは、期待値を持っているかどうか?だけ。
Spy: スタブと似ているが、更にどのように実行されたか?が記録されている。
> スパイとか呼んだことありません。
普段は、スタブとモックを使い分けることなんかしていなくて、「テスト時に使うダミー的なコードはすべてモック」(上に書いたやつ全部まとめて)と呼んでいる気がします。
Mockito と TestNG
TestNG は、単体テスト、機能テスト、総合テストなどすべてのカテゴリを網羅するテストフレームワーク(らしい)。
最初、TestNG って、「テスト失敗」のことだと思ってました。
NG って、NextGeneration から取っているみたいですが、英語圏の人からするとややこしくないのだろうか。
JUnitに似ているけど、拡張版ではないらしい。
ただ、JUnit4 の方が新しいフレームワークなので、今や NextGeneration とは呼びにくいでしょうけど。
まとめ
Mockito 初心者によって、分かりやすい内容だったと思います。
本当に単純なコードを一つ書いただけですが、最初のチュートリアルはこのくらいが良いのかもしれません。
用語については、あまり考えて使い分けていないことを改めて感じさせられました。
この辺は開発現場にかなり依存してしまっている気がします。
TestNG は今度試してみたいですが、コスト的に JUnit4 から移行するかどうかは分からないです。
歴史のあるフレームワークなのに、まだまだ知らないこと多いです。(耳にした覚えがなかったです)
特に海外では評価が高いフレームワークなので、海外の記事を呼んでいるとこういった情報が入ってくるかもしれませんね。
あと、assertj は面白そうです。
VirtualBox ゲストOS のネットワークが頻繁に Up/Down するが原因は分かっていない話
本件、今のところ結局解決していないので、解決策には言及していません。
(追記)解消しました。
VirtualBox ゲストOS のネットワークが頻繁に Up/Down していたが NATネットワークに変えたら解消 - 社内se × プログラマ × ビッグデータ
VirtualBox のゲストOSとして、lubuntu(ubuntu)を乗せているのだが、頻繁にネットワークが切断されてしまう。
ログを見てみると、1~2分に1回の頻度で Link の Up/Downが繰り返されている。
Down -> Up までは 5秒程度だが、ダウンしている間はネットワークに接続できず、Webページ一つ開くにも、再度クリックしなければならないことが頻繁にある。
$ less /var/log/kern.log | grep -e "NIC Link is Down" -e "NIC Link is Up" | head -20 Jul 30 21:34:49 xxx kernel: [ 38.396584] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:35:54 xxx kernel: [ 102.912681] e1000: enp0s3 NIC Link is Down Jul 30 21:36:00 xxx kernel: [ 108.962889] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:36:58 xxx kernel: [ 167.424237] e1000: enp0s3 NIC Link is Down Jul 30 21:37:02 xxx kernel: [ 171.456655] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:38:07 xxx kernel: [ 235.969067] e1000: enp0s3 NIC Link is Down Jul 30 21:38:11 xxx kernel: [ 240.004110] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:39:15 xxx kernel: [ 304.512446] e1000: enp0s3 NIC Link is Down Jul 30 21:39:22 xxx kernel: [ 310.656653] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:40:24 xxx kernel: [ 373.152291] e1000: enp0s3 NIC Link is Down Jul 30 21:40:30 xxx kernel: [ 379.200501] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:41:43 xxx kernel: [ 451.776211] e1000: enp0s3 NIC Link is Down Jul 30 21:41:49 xxx kernel: [ 457.888537] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:42:57 xxx kernel: [ 526.432210] e1000: enp0s3 NIC Link is Down Jul 30 21:43:03 xxx kernel: [ 532.480859] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:44:02 xxx kernel: [ 590.944245] e1000: enp0s3 NIC Link is Down Jul 30 21:44:08 xxx kernel: [ 596.992489] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:45:04 xxx kernel: [ 653.440290] e1000: enp0s3 NIC Link is Down Jul 30 21:45:10 xxx kernel: [ 659.488486] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX Jul 30 21:46:15 xxx kernel: [ 724.000182] e1000: enp0s3 NIC Link is Down
アダプタの状態を見てみても、Speed は 1000Mb/s 設定されているし、Duplex も Full (Halfになっていない)。
特に不安定になっているようには見えない。
$ ethtool enp0s3 Settings for enp0s3: Supported ports: [ TP ] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Supported pause frame use: No Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Advertised pause frame use: No Advertised auto-negotiation: Yes Speed: 1000Mb/s Duplex: Full Port: Twisted Pair PHYAD: 0 Transceiver: internal Auto-negotiation: on MDI-X: off (auto) Cannot get wake-on-lan settings: Operation not permitted Current message level: 0x00000007 (7) drv probe link Link detected: yes
ホストOSは Windows7 でネットワークにはUSB接続の無線LANアダプタを使用している。
※ゲストOSとのアダプタは NAT。
ハードウェア側に問題があるとすれば、このアダプタに問題があるか、あるいはルーターも古いので怪しいところはある。
ネットで検索してみると、似たような状況になっている記事や質問がいくつか見かけられたが、ハードウェアを疑っているケースも多かった。
すぐには試せないが、有線ケーブルで接続してみれば、案外それで安定はするのかもしれない。